feat: claude 3.7 model support
This commit is contained in:
parent
b7e26ba18f
commit
cdbbcb6ac3
|
@ -25,9 +25,10 @@ import { ANTHROPIC_BASE_URL } from "@/app/constant";
|
||||||
import {
|
import {
|
||||||
getMessageTextContent,
|
getMessageTextContent,
|
||||||
getWebReferenceMessageTextContent,
|
getWebReferenceMessageTextContent,
|
||||||
|
isClaudeThinkingModel,
|
||||||
isVisionModel,
|
isVisionModel,
|
||||||
} from "@/app/utils";
|
} from "@/app/utils";
|
||||||
import { preProcessImageContent, stream } from "@/app/utils/chat";
|
import { preProcessImageContent, streamWithThink } from "@/app/utils/chat";
|
||||||
import { cloudflareAIGatewayUrl } from "@/app/utils/cloudflare";
|
import { cloudflareAIGatewayUrl } from "@/app/utils/cloudflare";
|
||||||
import { RequestPayload } from "./openai";
|
import { RequestPayload } from "./openai";
|
||||||
import { fetch } from "@/app/utils/stream";
|
import { fetch } from "@/app/utils/stream";
|
||||||
|
@ -62,6 +63,10 @@ export interface AnthropicChatRequest {
|
||||||
top_k?: number; // Only sample from the top K options for each subsequent token.
|
top_k?: number; // Only sample from the top K options for each subsequent token.
|
||||||
metadata?: object; // An object describing metadata about the request.
|
metadata?: object; // An object describing metadata about the request.
|
||||||
stream?: boolean; // Whether to incrementally stream the response using server-sent events.
|
stream?: boolean; // Whether to incrementally stream the response using server-sent events.
|
||||||
|
thinking?: {
|
||||||
|
type: "enabled";
|
||||||
|
budget_tokens: number;
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ChatRequest {
|
export interface ChatRequest {
|
||||||
|
@ -269,10 +274,9 @@ export class ClaudeApi implements LLMApi {
|
||||||
return res?.content?.[0]?.text;
|
return res?.content?.[0]?.text;
|
||||||
}
|
}
|
||||||
async chat(options: ChatOptions): Promise<void> {
|
async chat(options: ChatOptions): Promise<void> {
|
||||||
|
const thinkingModel = isClaudeThinkingModel(options.config.model);
|
||||||
const visionModel = isVisionModel(options.config.model);
|
const visionModel = isVisionModel(options.config.model);
|
||||||
|
|
||||||
const accessStore = useAccessStore.getState();
|
const accessStore = useAccessStore.getState();
|
||||||
|
|
||||||
const shouldStream = !!options.config.stream;
|
const shouldStream = !!options.config.stream;
|
||||||
|
|
||||||
const modelConfig = {
|
const modelConfig = {
|
||||||
|
@ -376,6 +380,21 @@ export class ClaudeApi implements LLMApi {
|
||||||
top_k: 5,
|
top_k: 5,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// extended-thinking
|
||||||
|
// https://docs.anthropic.com/zh-CN/docs/build-with-claude/extended-thinking
|
||||||
|
if (
|
||||||
|
thinkingModel &&
|
||||||
|
useChatStore.getState().currentSession().mask.claudeThinking
|
||||||
|
) {
|
||||||
|
requestBody.thinking = {
|
||||||
|
type: "enabled",
|
||||||
|
budget_tokens: modelConfig.budget_tokens,
|
||||||
|
};
|
||||||
|
requestBody.temperature = undefined;
|
||||||
|
requestBody.top_p = undefined;
|
||||||
|
requestBody.top_k = undefined;
|
||||||
|
}
|
||||||
|
|
||||||
const path = this.path(Anthropic.ChatPath);
|
const path = this.path(Anthropic.ChatPath);
|
||||||
|
|
||||||
const controller = new AbortController();
|
const controller = new AbortController();
|
||||||
|
@ -390,7 +409,7 @@ export class ClaudeApi implements LLMApi {
|
||||||
// .getAsTools(
|
// .getAsTools(
|
||||||
// useChatStore.getState().currentSession().mask?.plugin || [],
|
// useChatStore.getState().currentSession().mask?.plugin || [],
|
||||||
// );
|
// );
|
||||||
return stream(
|
return streamWithThink(
|
||||||
path,
|
path,
|
||||||
requestBody,
|
requestBody,
|
||||||
{
|
{
|
||||||
|
@ -418,8 +437,9 @@ export class ClaudeApi implements LLMApi {
|
||||||
name: string;
|
name: string;
|
||||||
};
|
};
|
||||||
delta?: {
|
delta?: {
|
||||||
type: "text_delta" | "input_json_delta";
|
type: "text_delta" | "input_json_delta" | "thinking_delta";
|
||||||
text?: string;
|
text?: string;
|
||||||
|
thinking?: string;
|
||||||
partial_json?: string;
|
partial_json?: string;
|
||||||
};
|
};
|
||||||
index: number;
|
index: number;
|
||||||
|
@ -447,7 +467,24 @@ export class ClaudeApi implements LLMApi {
|
||||||
runTools[index]["function"]["arguments"] +=
|
runTools[index]["function"]["arguments"] +=
|
||||||
chunkJson?.delta?.partial_json;
|
chunkJson?.delta?.partial_json;
|
||||||
}
|
}
|
||||||
return chunkJson?.delta?.text;
|
|
||||||
|
console.log("chunkJson", chunkJson);
|
||||||
|
|
||||||
|
const isThinking = chunkJson?.delta?.type === "thinking_delta";
|
||||||
|
const content = isThinking
|
||||||
|
? chunkJson?.delta?.thinking
|
||||||
|
: chunkJson?.delta?.text;
|
||||||
|
|
||||||
|
if (!content || content.trim().length === 0) {
|
||||||
|
return {
|
||||||
|
isThinking: false,
|
||||||
|
content: "",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
isThinking,
|
||||||
|
content,
|
||||||
|
};
|
||||||
},
|
},
|
||||||
// processToolMessage, include tool_calls message and tool call results
|
// processToolMessage, include tool_calls message and tool call results
|
||||||
(
|
(
|
||||||
|
|
|
@ -54,6 +54,8 @@ import ReloadIcon from "../icons/reload.svg";
|
||||||
import HeadphoneIcon from "../icons/headphone.svg";
|
import HeadphoneIcon from "../icons/headphone.svg";
|
||||||
import SearchCloseIcon from "../icons/search_close.svg";
|
import SearchCloseIcon from "../icons/search_close.svg";
|
||||||
import SearchOpenIcon from "../icons/search_open.svg";
|
import SearchOpenIcon from "../icons/search_open.svg";
|
||||||
|
import EnableThinkingIcon from "../icons/thinking_enable.svg";
|
||||||
|
import DisableThinkingIcon from "../icons/thinking_disable.svg";
|
||||||
import {
|
import {
|
||||||
ChatMessage,
|
ChatMessage,
|
||||||
SubmitKey,
|
SubmitKey,
|
||||||
|
@ -82,6 +84,7 @@ import {
|
||||||
isSupportRAGModel,
|
isSupportRAGModel,
|
||||||
isFunctionCallModel,
|
isFunctionCallModel,
|
||||||
isFirefox,
|
isFirefox,
|
||||||
|
isClaudeThinkingModel,
|
||||||
} from "../utils";
|
} from "../utils";
|
||||||
|
|
||||||
import { uploadImage as uploadImageRemote } from "@/app/utils/chat";
|
import { uploadImage as uploadImageRemote } from "@/app/utils/chat";
|
||||||
|
@ -511,6 +514,14 @@ export function ChatActions(props: {
|
||||||
const pluginStore = usePluginStore();
|
const pluginStore = usePluginStore();
|
||||||
const session = chatStore.currentSession();
|
const session = chatStore.currentSession();
|
||||||
|
|
||||||
|
// switch thinking mode
|
||||||
|
const claudeThinking = chatStore.currentSession().mask.claudeThinking;
|
||||||
|
function switchClaudeThinking() {
|
||||||
|
chatStore.updateTargetSession(session, (session) => {
|
||||||
|
session.mask.claudeThinking = !session.mask.claudeThinking;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// switch web search
|
// switch web search
|
||||||
const webSearch = chatStore.currentSession().mask.webSearch;
|
const webSearch = chatStore.currentSession().mask.webSearch;
|
||||||
function switchWebSearch() {
|
function switchWebSearch() {
|
||||||
|
@ -741,6 +752,7 @@ export function ChatActions(props: {
|
||||||
text={currentModelName}
|
text={currentModelName}
|
||||||
icon={<RobotIcon />}
|
icon={<RobotIcon />}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{!isFunctionCallModel(currentModel) && isEnableWebSearch && (
|
{!isFunctionCallModel(currentModel) && isEnableWebSearch && (
|
||||||
<ChatAction
|
<ChatAction
|
||||||
onClick={switchWebSearch}
|
onClick={switchWebSearch}
|
||||||
|
@ -753,6 +765,20 @@ export function ChatActions(props: {
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{isClaudeThinkingModel(currentModel) && (
|
||||||
|
<ChatAction
|
||||||
|
onClick={switchClaudeThinking}
|
||||||
|
text={
|
||||||
|
claudeThinking
|
||||||
|
? Locale.Chat.InputActions.DisableThinking
|
||||||
|
: Locale.Chat.InputActions.EnableThinking
|
||||||
|
}
|
||||||
|
icon={
|
||||||
|
claudeThinking ? <EnableThinkingIcon /> : <DisableThinkingIcon />
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
{showModelSelector && (
|
{showModelSelector && (
|
||||||
<SearchSelector
|
<SearchSelector
|
||||||
defaultSelectedValue={`${currentModel}@${currentProviderName}`}
|
defaultSelectedValue={`${currentModel}@${currentProviderName}`}
|
||||||
|
|
|
@ -110,6 +110,29 @@ export function ModelConfigList(props: {
|
||||||
></input>
|
></input>
|
||||||
</ListItem>
|
</ListItem>
|
||||||
|
|
||||||
|
{props.modelConfig?.providerName === ServiceProvider.Anthropic && (
|
||||||
|
<ListItem
|
||||||
|
title={Locale.Settings.BudgetTokens.Title}
|
||||||
|
subTitle={Locale.Settings.BudgetTokens.SubTitle}
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
aria-label={Locale.Settings.BudgetTokens.Title}
|
||||||
|
type="number"
|
||||||
|
min={1024}
|
||||||
|
max={32000}
|
||||||
|
value={props.modelConfig.budget_tokens}
|
||||||
|
onChange={(e) =>
|
||||||
|
props.updateConfig(
|
||||||
|
(config) =>
|
||||||
|
(config.budget_tokens = ModalConfigValidator.budget_tokens(
|
||||||
|
e.currentTarget.valueAsNumber,
|
||||||
|
)),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
></input>
|
||||||
|
</ListItem>
|
||||||
|
)}
|
||||||
|
|
||||||
{props.modelConfig?.providerName == ServiceProvider.Google ? null : (
|
{props.modelConfig?.providerName == ServiceProvider.Google ? null : (
|
||||||
<>
|
<>
|
||||||
<ListItem
|
<ListItem
|
||||||
|
|
|
@ -381,7 +381,6 @@ const googleModels = [
|
||||||
];
|
];
|
||||||
|
|
||||||
const anthropicModels = [
|
const anthropicModels = [
|
||||||
"claude-instant-1.2",
|
|
||||||
"claude-2.0",
|
"claude-2.0",
|
||||||
"claude-2.1",
|
"claude-2.1",
|
||||||
"claude-3-sonnet-20240229",
|
"claude-3-sonnet-20240229",
|
||||||
|
@ -393,6 +392,8 @@ const anthropicModels = [
|
||||||
"claude-3-5-sonnet-20240620",
|
"claude-3-5-sonnet-20240620",
|
||||||
"claude-3-5-sonnet-20241022",
|
"claude-3-5-sonnet-20241022",
|
||||||
"claude-3-5-sonnet-latest",
|
"claude-3-5-sonnet-latest",
|
||||||
|
"claude-3-7-sonnet-20250219",
|
||||||
|
"claude-3-7-sonnet-latest",
|
||||||
];
|
];
|
||||||
|
|
||||||
const baiduModels = [
|
const baiduModels = [
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="16" height="16" fill="currentColor"><path d="M9.97308 18H14.0269C14.1589 16.7984 14.7721 15.8065 15.7676 14.7226C15.8797 14.6006 16.5988 13.8564 16.6841 13.7501C17.5318 12.6931 18 11.385 18 10C18 6.68629 15.3137 4 12 4C8.68629 4 6 6.68629 6 10C6 11.3843 6.46774 12.6917 7.31462 13.7484C7.40004 13.855 8.12081 14.6012 8.23154 14.7218C9.22766 15.8064 9.84103 16.7984 9.97308 18ZM14 20H10V21H14V20ZM5.75395 14.9992C4.65645 13.6297 4 11.8915 4 10C4 5.58172 7.58172 2 12 2C16.4183 2 20 5.58172 20 10C20 11.8925 19.3428 13.6315 18.2443 15.0014C17.624 15.7748 16 17 16 18.5V21C16 22.1046 15.1046 23 14 23H10C8.89543 23 8 22.1046 8 21V18.5C8 17 6.37458 15.7736 5.75395 14.9992ZM13 10.0048H15.5L11 16.0048V12.0048H8.5L13 6V10.0048Z"></path></svg>
|
After Width: | Height: | Size: 804 B |
|
@ -0,0 +1 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="16" height="16" fill="currentColor"><path d="M7.94101 18C7.64391 16.7274 6.30412 15.6857 5.75395 14.9992C4.65645 13.6297 4 11.8915 4 10C4 5.58172 7.58172 2 12 2C16.4183 2 20 5.58172 20 10C20 11.8925 19.3428 13.6315 18.2443 15.0014C17.6944 15.687 16.3558 16.7276 16.059 18H7.94101ZM16 20V21C16 22.1046 15.1046 23 14 23H10C8.89543 23 8 22.1046 8 21V20H16ZM13 10.0048V6L8.5 12.0048H11V16.0048L15.5 10.0048H13Z"></path></svg>
|
After Width: | Height: | Size: 488 B |
|
@ -75,6 +75,8 @@ const cn = {
|
||||||
UploadFle: "上传文件",
|
UploadFle: "上传文件",
|
||||||
OpenWebSearch: "开启联网",
|
OpenWebSearch: "开启联网",
|
||||||
CloseWebSearch: "关闭联网",
|
CloseWebSearch: "关闭联网",
|
||||||
|
EnableThinking: "开启思考",
|
||||||
|
DisableThinking: "关闭思考",
|
||||||
},
|
},
|
||||||
Rename: "重命名对话",
|
Rename: "重命名对话",
|
||||||
Typing: "正在输入…",
|
Typing: "正在输入…",
|
||||||
|
@ -545,6 +547,11 @@ const cn = {
|
||||||
Title: "单次回复限制 (max_tokens)",
|
Title: "单次回复限制 (max_tokens)",
|
||||||
SubTitle: "单次交互所用的最大 Token 数",
|
SubTitle: "单次交互所用的最大 Token 数",
|
||||||
},
|
},
|
||||||
|
BudgetTokens: {
|
||||||
|
Title: "扩展思考预算限制 (budget_tokens)",
|
||||||
|
SubTitle:
|
||||||
|
"内部推理过程中允许使用的最大令牌数,budget_tokens 必须始终小于 max_tokens。",
|
||||||
|
},
|
||||||
PresencePenalty: {
|
PresencePenalty: {
|
||||||
Title: "话题新鲜度 (presence_penalty)",
|
Title: "话题新鲜度 (presence_penalty)",
|
||||||
SubTitle: "值越大,越有可能扩展到新话题",
|
SubTitle: "值越大,越有可能扩展到新话题",
|
||||||
|
|
|
@ -77,6 +77,8 @@ const en: LocaleType = {
|
||||||
UploadFle: "Upload Files",
|
UploadFle: "Upload Files",
|
||||||
OpenWebSearch: "Enable Web Search",
|
OpenWebSearch: "Enable Web Search",
|
||||||
CloseWebSearch: "Disable Web Search",
|
CloseWebSearch: "Disable Web Search",
|
||||||
|
EnableThinking: "Enable Thinking",
|
||||||
|
DisableThinking: "Disable Thinking",
|
||||||
},
|
},
|
||||||
Rename: "Rename Chat",
|
Rename: "Rename Chat",
|
||||||
Typing: "Typing…",
|
Typing: "Typing…",
|
||||||
|
@ -550,6 +552,11 @@ const en: LocaleType = {
|
||||||
Title: "Max Tokens",
|
Title: "Max Tokens",
|
||||||
SubTitle: "Maximum length of input tokens and generated tokens",
|
SubTitle: "Maximum length of input tokens and generated tokens",
|
||||||
},
|
},
|
||||||
|
BudgetTokens: {
|
||||||
|
Title: "Budget Tokens",
|
||||||
|
SubTitle:
|
||||||
|
"The budget_tokens parameter determines the maximum number of tokens Claude is allowed use for its internal reasoning process. budget_tokens must always be less than the max_tokens specified.",
|
||||||
|
},
|
||||||
PresencePenalty: {
|
PresencePenalty: {
|
||||||
Title: "Presence Penalty",
|
Title: "Presence Penalty",
|
||||||
SubTitle:
|
SubTitle:
|
||||||
|
|
|
@ -73,6 +73,7 @@ export const DEFAULT_CONFIG = {
|
||||||
temperature: 0.5,
|
temperature: 0.5,
|
||||||
top_p: 1,
|
top_p: 1,
|
||||||
max_tokens: 4000,
|
max_tokens: 4000,
|
||||||
|
budget_tokens: 1024,
|
||||||
presence_penalty: 0,
|
presence_penalty: 0,
|
||||||
frequency_penalty: 0,
|
frequency_penalty: 0,
|
||||||
sendMemory: true,
|
sendMemory: true,
|
||||||
|
@ -170,6 +171,9 @@ export const ModalConfigValidator = {
|
||||||
max_tokens(x: number) {
|
max_tokens(x: number) {
|
||||||
return limitNumber(x, 0, 512000, 1024);
|
return limitNumber(x, 0, 512000, 1024);
|
||||||
},
|
},
|
||||||
|
budget_tokens(x: number) {
|
||||||
|
return limitNumber(x, 0, 32000, 1024);
|
||||||
|
},
|
||||||
presence_penalty(x: number) {
|
presence_penalty(x: number) {
|
||||||
return limitNumber(x, -2, 2, 0);
|
return limitNumber(x, -2, 2, 0);
|
||||||
},
|
},
|
||||||
|
|
|
@ -19,6 +19,7 @@ export type Mask = {
|
||||||
builtin: boolean;
|
builtin: boolean;
|
||||||
usePlugins?: boolean;
|
usePlugins?: boolean;
|
||||||
webSearch?: boolean;
|
webSearch?: boolean;
|
||||||
|
claudeThinking?: boolean;
|
||||||
// 上游插件业务参数
|
// 上游插件业务参数
|
||||||
plugin?: string[];
|
plugin?: string[];
|
||||||
enableArtifacts?: boolean;
|
enableArtifacts?: boolean;
|
||||||
|
|
10
app/utils.ts
10
app/utils.ts
|
@ -401,6 +401,8 @@ export function isFunctionCallModel(modelName: string) {
|
||||||
"claude-3-5-sonnet-20241022",
|
"claude-3-5-sonnet-20241022",
|
||||||
"claude-3-5-sonnet-latest",
|
"claude-3-5-sonnet-latest",
|
||||||
"claude-3-5-haiku-latest",
|
"claude-3-5-haiku-latest",
|
||||||
|
"claude-3-7-sonnet-20250219",
|
||||||
|
"claude-3-7-sonnet-latest",
|
||||||
];
|
];
|
||||||
if (specialModels.some((keyword) => modelName === keyword)) return true;
|
if (specialModels.some((keyword) => modelName === keyword)) return true;
|
||||||
return DEFAULT_MODELS.filter(
|
return DEFAULT_MODELS.filter(
|
||||||
|
@ -408,6 +410,14 @@ export function isFunctionCallModel(modelName: string) {
|
||||||
).some((model) => model.name === modelName);
|
).some((model) => model.name === modelName);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function isClaudeThinkingModel(modelName: string) {
|
||||||
|
const specialModels = [
|
||||||
|
"claude-3-7-sonnet-20250219",
|
||||||
|
"claude-3-7-sonnet-latest",
|
||||||
|
];
|
||||||
|
return specialModels.some((keyword) => modelName === keyword);
|
||||||
|
}
|
||||||
|
|
||||||
export function fetch(
|
export function fetch(
|
||||||
url: string,
|
url: string,
|
||||||
options?: Record<string, unknown>,
|
options?: Record<string, unknown>,
|
||||||
|
|
Loading…
Reference in New Issue