add deferred tools support for kimi in openai-completions api
This commit is contained in:
@@ -77,6 +77,26 @@ function hasToolHistory(messages: Message[]): boolean {
|
||||
return false;
|
||||
}
|
||||
|
||||
function getDeferredToolNames(messages: Message[]): Set<string> {
|
||||
const names = new Set<string>();
|
||||
for (const message of messages) {
|
||||
if (message.role === "toolResult") {
|
||||
for (const name of message.addedToolNames ?? []) {
|
||||
names.add(name);
|
||||
}
|
||||
}
|
||||
}
|
||||
return names;
|
||||
}
|
||||
|
||||
function getToolsByName(tools: Tool[] | undefined, names: Iterable<string>): Tool[] {
|
||||
if (!tools) return [];
|
||||
const toolsByName = new Map(tools.map((tool) => [tool.name, tool]));
|
||||
return Array.from(names)
|
||||
.map((name) => toolsByName.get(name))
|
||||
.filter((tool): tool is Tool => tool !== undefined);
|
||||
}
|
||||
|
||||
function isTextContentBlock(block: { type: string }): block is TextContent {
|
||||
return block.type === "text";
|
||||
}
|
||||
@@ -117,14 +137,23 @@ interface OpenAICompatCacheControl {
|
||||
ttl?: string;
|
||||
}
|
||||
|
||||
type ResolvedOpenAICompletionsCompat = Omit<Required<OpenAICompletionsCompat>, "cacheControlFormat"> & {
|
||||
type ResolvedOpenAICompletionsCompat = Omit<
|
||||
Required<OpenAICompletionsCompat>,
|
||||
"cacheControlFormat" | "deferredToolsMode"
|
||||
> & {
|
||||
cacheControlFormat?: OpenAICompletionsCompat["cacheControlFormat"];
|
||||
deferredToolsMode?: OpenAICompletionsCompat["deferredToolsMode"];
|
||||
};
|
||||
|
||||
type ResolvedChatTemplateKwargValue = string | number | boolean | null;
|
||||
|
||||
type ChatCompletionInstructionMessageParam = ChatCompletionDeveloperMessageParam | ChatCompletionSystemMessageParam;
|
||||
|
||||
type KimiToolSystemMessageParam = {
|
||||
role: "system";
|
||||
tools: OpenAI.Chat.Completions.ChatCompletionTool[];
|
||||
};
|
||||
|
||||
type OpenAIEncryptedReasoningDetail = {
|
||||
type: "reasoning.encrypted";
|
||||
id: string;
|
||||
@@ -585,8 +614,11 @@ function buildParams(
|
||||
params.temperature = options.temperature;
|
||||
}
|
||||
|
||||
if (context.tools && context.tools.length > 0) {
|
||||
params.tools = convertTools(context.tools, compat);
|
||||
const deferredToolNames =
|
||||
compat.deferredToolsMode === "kimi" ? getDeferredToolNames(context.messages) : new Set<string>();
|
||||
const activeTools = context.tools?.filter((tool) => !deferredToolNames.has(tool.name));
|
||||
if (activeTools && activeTools.length > 0) {
|
||||
params.tools = convertTools(activeTools, compat);
|
||||
if (compat.zaiToolStream) {
|
||||
(params as any).tool_stream = true;
|
||||
}
|
||||
@@ -1025,6 +1057,7 @@ export function convertMessages(
|
||||
params.push(assistantMsg);
|
||||
} else if (msg.role === "toolResult") {
|
||||
const imageBlocks: Array<{ type: "image_url"; image_url: { url: string } }> = [];
|
||||
const deferredToolNames = new Set<string>();
|
||||
let j = i;
|
||||
|
||||
for (; j < transformedMessages.length && transformedMessages[j].role === "toolResult"; j++) {
|
||||
@@ -1051,6 +1084,12 @@ export function convertMessages(
|
||||
}
|
||||
params.push(toolResultMsg);
|
||||
|
||||
if (compat.deferredToolsMode === "kimi") {
|
||||
for (const name of toolMsg.addedToolNames ?? []) {
|
||||
deferredToolNames.add(name);
|
||||
}
|
||||
}
|
||||
|
||||
if (hasImages && model.input.includes("image")) {
|
||||
for (const block of toolMsg.content) {
|
||||
if (isImageContentBlock(block)) {
|
||||
@@ -1089,6 +1128,18 @@ export function convertMessages(
|
||||
} else {
|
||||
lastRole = "toolResult";
|
||||
}
|
||||
|
||||
if (deferredToolNames.size > 0) {
|
||||
const deferredTools = getToolsByName(context.tools, deferredToolNames);
|
||||
if (deferredTools.length > 0) {
|
||||
const kimiToolMessage: KimiToolSystemMessageParam = {
|
||||
role: "system",
|
||||
tools: convertTools(deferredTools, compat),
|
||||
};
|
||||
// Kimi accepts a system message with tools but omits the standard content field.
|
||||
params.push(kimiToolMessage as unknown as ChatCompletionMessageParam);
|
||||
}
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -1256,6 +1307,7 @@ function detectCompat(model: Model<"openai-completions">): ResolvedOpenAIComplet
|
||||
supportsStrictMode: !isMoonshot && !isTogether && !isCloudflareAiGateway && !isNvidia,
|
||||
cacheControlFormat,
|
||||
sendSessionAffinityHeaders: false,
|
||||
deferredToolsMode: undefined,
|
||||
sessionAffinityFormat: isOpenRouter ? "openrouter" : "openai",
|
||||
supportsLongCacheRetention: !(
|
||||
isTogether ||
|
||||
@@ -1296,6 +1348,7 @@ function getCompat(model: Model<"openai-completions">): ResolvedOpenAICompletion
|
||||
supportsStrictMode: model.compat.supportsStrictMode ?? detected.supportsStrictMode,
|
||||
cacheControlFormat: model.compat.cacheControlFormat ?? detected.cacheControlFormat,
|
||||
sendSessionAffinityHeaders: model.compat.sendSessionAffinityHeaders ?? detected.sendSessionAffinityHeaders,
|
||||
deferredToolsMode: model.compat.deferredToolsMode ?? detected.deferredToolsMode,
|
||||
sessionAffinityFormat: model.compat.sessionAffinityFormat ?? detected.sessionAffinityFormat,
|
||||
supportsLongCacheRetention: model.compat.supportsLongCacheRetention ?? detected.supportsLongCacheRetention,
|
||||
};
|
||||
|
||||
@@ -524,6 +524,8 @@ export interface OpenAICompletionsCompat {
|
||||
cacheControlFormat?: "anthropic";
|
||||
/** Whether to send session-affinity data from `options.sessionId`. Default: false. */
|
||||
sendSessionAffinityHeaders?: boolean;
|
||||
/** Provider-specific deferred tool serialization mode. */
|
||||
deferredToolsMode?: "kimi";
|
||||
/** Session-affinity header format: `openai` sends `session_id`, `x-client-request-id`, and `x-session-affinity`; `openai-nosession` sends `x-client-request-id` and `x-session-affinity`; `openrouter` sends `x-session-id`. Does not affect the `prompt_cache_key` body param, which is governed by cache retention. Default: auto-detected. */
|
||||
sessionAffinityFormat?: SessionAffinityFormat;
|
||||
/** Whether the provider supports long prompt cache retention (`prompt_cache_retention: "24h"` or Anthropic-style `cache_control.ttl: "1h"`, depending on format). Default: true. */
|
||||
|
||||
Reference in New Issue
Block a user