Skip to content
Merged
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
8 changes: 8 additions & 0 deletions src/config/aiModels/minimax.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ const minimaxChatModels: AIChatModelCard[] = [
{
abilities: {
functionCall: true,
search: true,
vision: true,
},
contextWindowTokens: 1_000_192,
Expand All @@ -19,11 +20,15 @@ const minimaxChatModels: AIChatModelCard[] = [
output: 8,
},
releasedAt: '2025-01-15',
settings: {
searchImpl: 'params',
},
type: 'chat',
},
{
abilities: {
functionCall: true,
search: true,
vision: true,
},
contextWindowTokens: 245_760,
Expand All @@ -37,6 +42,9 @@ const minimaxChatModels: AIChatModelCard[] = [
input: 1,
output: 1,
},
settings: {
searchImpl: 'params',
},
type: 'chat',
},
{
Expand Down
52 changes: 46 additions & 6 deletions src/config/aiModels/openai.ts
Original file line number Diff line number Diff line change
Expand Up @@ -236,6 +236,26 @@ export const openaiChatModels: AIChatModelCard[] = [
releasedAt: '2024-07-18',
type: 'chat',
},
{
abilities: {
search: true,
},
contextWindowTokens: 128_000,
description:
'GPT-4o mini 搜索预览版是一个专门训练用于理解和执行网页搜索查询的模型,使用的是 Chat Completions API。除了令牌费用之外,网页搜索查询还会按每次工具调用收取费用。',
displayName: 'GPT-4o mini Search Preview',
id: 'gpt-4o-mini-search-preview',
maxOutput: 16_384,
pricing: {
input: 0.15,
output: 0.6,
},
releasedAt: '2025-03-11',
settings: {
searchImpl: 'internal',
},
type: 'chat',
},
{
abilities: {
functionCall: true,
Expand All @@ -244,14 +264,34 @@ export const openaiChatModels: AIChatModelCard[] = [
contextWindowTokens: 128_000,
description:
'ChatGPT-4o 是一款动态模型,实时更新以保持当前最新版本。它结合了强大的语言理解与生成能力,适合于大规模应用场景,包括客户服务、教育和技术支持。',
displayName: 'GPT-4o 1120',
id: 'gpt-4o-2024-11-20',
displayName: 'GPT-4o',
id: 'gpt-4o',
pricing: {
cachedInput: 1.25,
input: 2.5,
output: 10,
},
releasedAt: '2024-11-20',
releasedAt: '2024-05-13',
type: 'chat',
},
{
abilities: {
search: true,
},
contextWindowTokens: 128_000,
description:
'GPT-4o 搜索预览版是一个专门训练用于理解和执行网页搜索查询的模型,使用的是 Chat Completions API。除了令牌费用之外,网页搜索查询还会按每次工具调用收取费用。',
displayName: 'GPT-4o Search Preview',
id: 'gpt-4o-search-preview',
maxOutput: 16_384,
pricing: {
input: 2.5,
output: 10,
},
releasedAt: '2025-03-11',
settings: {
searchImpl: 'internal',
},
type: 'chat',
},
{
Expand All @@ -262,14 +302,14 @@ export const openaiChatModels: AIChatModelCard[] = [
contextWindowTokens: 128_000,
description:
'ChatGPT-4o 是一款动态模型,实时更新以保持当前最新版本。它结合了强大的语言理解与生成能力,适合于大规模应用场景,包括客户服务、教育和技术支持。',
displayName: 'GPT-4o',
id: 'gpt-4o',
displayName: 'GPT-4o 1120',
id: 'gpt-4o-2024-11-20',
pricing: {
cachedInput: 1.25,
input: 2.5,
output: 10,
},
releasedAt: '2024-05-13',
releasedAt: '2024-11-20',
type: 'chat',
},
{
Expand Down
12 changes: 11 additions & 1 deletion src/libs/model-runtime/minimax/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,24 @@ export const LobeMinimaxAI = LobeOpenAICompatibleFactory({
baseURL: 'https://api.minimax.chat/v1',
chatCompletion: {
handlePayload: (payload) => {
const { max_tokens, temperature, top_p, ...params } = payload;
const { enabledSearch, max_tokens, temperature, tools, top_p, ...params } = payload;

const minimaxTools = enabledSearch
? [
...(tools || []),
{
type: 'web_search',
},
]
: tools;

return {
...params,
frequency_penalty: undefined,
max_tokens: max_tokens !== undefined ? max_tokens : getMinimaxMaxOutputs(payload.model),
presence_penalty: undefined,
temperature: temperature === undefined || temperature <= 0 ? undefined : temperature / 2,
tools: minimaxTools,
top_p: top_p !== undefined && top_p > 0 && top_p <= 1 ? top_p : undefined,
} as any;
},
Expand Down
9 changes: 8 additions & 1 deletion src/libs/model-runtime/openai/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,14 +21,21 @@ export const LobeOpenAI = LobeOpenAICompatibleFactory({
}

if (model.includes('-search-')) {
const oaiSearchContextSize = process.env.OPENAI_SEARCH_CONTEXT_SIZE; // low, medium, high

return {
...payload,
frequency_penalty: undefined,
presence_penalty: undefined,
stream: payload.stream ?? true,
temperature: undefined,
top_p: undefined,
};
...(oaiSearchContextSize && {
web_search_options: {
search_context_size: oaiSearchContextSize,
},
}),
} as any;
}

return { ...payload, stream: payload.stream ?? true };
Expand Down
88 changes: 79 additions & 9 deletions src/libs/model-runtime/utils/streams/openai.ts
Original file line number Diff line number Diff line change
Expand Up @@ -99,16 +99,87 @@ export const transformOpenAIStream = (
if (item.finish_reason) {
// one-api 的流式接口,会出现既有 finish_reason ,也有 content 的情况
// {"id":"demo","model":"deepl-en","choices":[{"index":0,"delta":{"role":"assistant","content":"Introduce yourself."},"finish_reason":"stop"}]}

if (typeof item.delta?.content === 'string' && !!item.delta.content) {
// MiniMax 内建搜索功能会在第一个 tools 流中 content 返回引用源,需要忽略
// {"id":"0483748a25071c611e2f48d2982fbe96","choices":[{"finish_reason":"stop","index":0,"delta":{"content":"[{\"no\":1,\"url\":\"https://www.xiaohongshu.com/discovery/item/66d8de3c000000001f01e752\",\"title\":\"郑钦文为国而战,没有理由不坚持🏅\",\"content\":\"·2024年08月03日\\n中国队选手郑钦文夺得巴黎奥运会网球女单比赛金牌(巴黎奥运第16金)\\n#巴黎奥运会[话题]# #郑钦文[话题]# #人物素材积累[话题]# #作文素材积累[话题]# #申论素材[话题]#\",\"web_icon\":\"https://www.xiaohongshu.com/favicon.ico\"}]","role":"tool","tool_call_id":"call_function_6696730535"}}],"created":1748255114,"model":"abab6.5s-chat","object":"chat.completion.chunk","usage":{"total_tokens":0,"total_characters":0},"input_sensitive":false,"output_sensitive":false,"input_sensitive_type":0,"output_sensitive_type":0,"output_sensitive_int":0}
if (typeof item.delta?.role === 'string' && item.delta.role === 'tool') {
return { data: null, id: chunk.id, type: 'text' };
}

return { data: item.delta.content, id: chunk.id, type: 'text' };
}

// OpenAI Search Preview 模型返回引用源
// {"id":"chatcmpl-18037d13-243c-4941-8b05-9530b352cf17","object":"chat.completion.chunk","created":1748351805,"model":"gpt-4o-mini-search-preview-2025-03-11","choices":[{"index":0,"delta":{"annotations":[{"type":"url_citation","url_citation":{"url":"https://zh.wikipedia.org/wiki/%E4%B8%8A%E6%B5%B7%E4%B9%90%E9%AB%98%E4%B9%90%E5%9B%AD?utm_source=openai","title":"上海乐高乐园","start_index":75,"end_index":199}}]},"finish_reason":"stop"}],"service_tier":"default"}
if ((item as any).delta?.annotations && (item as any).delta.annotations.length > 0) {
const citations = (item as any).delta.annotations;

return [
{
data: {
citations: citations.map(
(item: any) =>
({
title: item.url_citation.title,
url: item.url_citation.url,
}) as CitationItem,
),
},
id: chunk.id,
type: 'grounding',
},
];
}

// MiniMax 内建搜索功能会在最后一个流中的 message 数组中返回 4 个 Object,其中最后一个为 annotations
// {"id":"0483bf14ba55225a66de2342a21b4003","choices":[{"finish_reason":"tool_calls","index":0,"messages":[{"content":"","role":"user","reasoning_content":""},{"content":"","role":"assistant","tool_calls":[{"id":"call_function_0872338692","type":"web_search","function":{"name":"get_search_result","arguments":"{\"query_tag\":[\"天气\"],\"query_list\":[\"上海 2025年5月26日 天气\"]}"}}],"reasoning_content":""},{"content":"","role":"tool","tool_call_id":"call_function_0872338692","reasoning_content":""},{"content":"","role":"assistant","name":"海螺AI","annotations":[{"text":"【5†source】","url":"https://mtianqi.eastday.com/tianqi/shanghai/20250526.html","quote":"上海天气预报提供上海2025年05月26日天气"}],"audio_content":"","reasoning_content":""}]}],"created":1748274196,"model":"MiniMax-Text-01","object":"chat.completion","usage":{"total_tokens":13110,"total_characters":0,"prompt_tokens":12938,"completion_tokens":172},"base_resp":{"status_code":0,"status_msg":"Invalid parameters detected, json: unknown field \"user\""}}
if ((item as any).messages && (item as any).messages.length > 0) {
const citations = (item as any).messages.at(-1).annotations;
Comment on lines +136 to +137
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

logic: Potential null reference error if annotations is undefined in the last message

Suggested change
if ((item as any).messages && (item as any).messages.length > 0) {
const citations = (item as any).messages.at(-1).annotations;
if ((item as any).messages && (item as any).messages.length > 0) {
const lastMessage = (item as any).messages.at(-1);
const citations = lastMessage?.annotations;
if (!citations) return [];


return [
{
data: {
citations: citations.map(
(item: any) =>
({
title: item.url,
url: item.url,
Comment on lines +145 to +146
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

style: Using URL as both title and URL may be confusing for users - consider using item.text for title

}) as CitationItem,
),
},
id: chunk.id,
type: 'grounding',
},
];
}

if (chunk.usage) {
const usage = chunk.usage;
return { data: convertUsage(usage), id: chunk.id, type: 'usage' };
}

// xAI Live Search 功能返回引用源
// {"id":"8721eebb-6465-4c47-ba2e-8e2ec0f97055","object":"chat.completion.chunk","created":1747809109,"model":"grok-3","choices":[{"index":0,"delta":{"role":"assistant"},"finish_reason":"stop"}],"system_fingerprint":"fp_1affcf9872","citations":["https://world.huanqiu.com/"]}
if ((chunk as any).citations) {
const citations = (chunk as any).citations;

return [
{
data: {
citations: citations.map(
(item: any) =>
({
title: item,
url: item,
}) as CitationItem,
),
},
id: chunk.id,
type: 'grounding',
},
];
}

return { data: item.finish_reason, id: chunk.id, type: 'stop' };
}

Expand Down Expand Up @@ -146,21 +217,20 @@ export const transformOpenAIStream = (
// in Hunyuan api, the citation is in every chunk
('search_info' in chunk && (chunk.search_info as any)?.search_results) ||
// in Wenxin api, the citation is in the first and last chunk
('search_results' in chunk && chunk.search_results);
('search_results' in chunk && chunk.search_results) ||
// in Zhipu api, the citation is in the first chunk
('web_search' in chunk && chunk.web_search);

if (citations) {
streamContext.returnedCitation = true;

return [
{
data: {
citations: (citations as any[]).map(
(item) =>
({
title: typeof item === 'string' ? item : item.title,
url: typeof item === 'string' ? item : item.url,
}) as CitationItem,
),
citations: (citations as any[]).map((item) => ({
title: typeof item === 'string' ? item : item.title,
url: typeof item === 'string' ? item : item.url || item.link,
})).filter(c => c.title && c.url), // Zhipu 内建搜索工具有时会返回空 link 引发程序崩溃
},
id: chunk.id,
type: 'grounding',
Expand Down
3 changes: 3 additions & 0 deletions src/libs/model-runtime/zhipu/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@ export const LobeZhipuAI = LobeOpenAICompatibleFactory({
type: 'web_search',
web_search: {
enable: true,
result_sequence: 'before', // 将搜索结果返回顺序更改为 before 适配最小化 OpenAIStream 改动
search_engine: process.env.ZHIPU_SEARCH_ENGINE || 'search_std', // search_std, search_pro
search_result: true,
},
},
]
Expand Down
Loading