mirror of
https://github.com/badlogic/pi-mono.git
synced 2026-11-05 23:37:25 +00:00
fix(ai): remove unnecessary LongCat content compatibility
This commit is contained in:
parent
83124c38c2
commit
13c4e5264d
10 changed files with 3 additions and 79 deletions
|
|
@ -1461,7 +1461,6 @@ interface OpenAICompletionsCompat {
|
|||
maxTokensField?: 'max_completion_tokens' | 'max_tokens'; // Which field name to use (default: max_completion_tokens)
|
||||
requiresToolResultName?: boolean; // Whether tool results require the `name` field (default: false)
|
||||
requiresAssistantAfterToolResult?: boolean; // Whether tool results must be followed by an assistant message (default: false)
|
||||
requiresAssistantContentString?: boolean; // Whether tool-only assistant messages use empty string instead of null content (default: false)
|
||||
requiresThinkingAsText?: boolean; // Whether thinking blocks must be converted to text (default: false)
|
||||
requiresReasoningContentOnAssistantMessages?: boolean; // Whether all replayed assistant messages must include empty reasoning_content when reasoning is enabled (default: auto-detected for DeepSeek)
|
||||
thinkingFormat?: 'openai' | 'openrouter' | 'deepseek' | 'together' | 'baseten' | 'zai' | 'qwen' | 'chat-template' | 'qwen-chat-template' | 'string-thinking' | 'ant-ling'; // Format for reasoning param: 'openai' uses reasoning_effort, 'openrouter' uses reasoning: { effort }, 'deepseek' uses thinking: { type } plus reasoning_effort when supported, 'together' uses reasoning: { enabled } plus reasoning_effort when supported, 'baseten' uses configurable chat_template_args plus reasoning_effort when supported, 'zai' uses thinking: { type }, 'qwen' uses enable_thinking, 'chat-template' uses configurable chat_template_kwargs, 'qwen-chat-template' uses chat_template_kwargs.enable_thinking and preserve_thinking, 'string-thinking' uses top-level thinking, 'ant-ling' uses reasoning: { effort } only for mapped efforts (default: openai)
|
||||
|
|
|
|||
|
|
@ -32,7 +32,6 @@ export default defineProvider({
|
|||
supportsDeveloperRole: false,
|
||||
supportsReasoningEffort: false,
|
||||
maxTokensField: "max_tokens",
|
||||
requiresAssistantContentString: true,
|
||||
requiresReasoningContentOnAssistantMessages: true,
|
||||
thinkingFormat: "deepseek",
|
||||
supportsStrictMode: false,
|
||||
|
|
|
|||
|
|
@ -1281,7 +1281,7 @@ export function convertMessages(
|
|||
// Some providers don't accept null content, use empty string instead
|
||||
const assistantMsg: ChatCompletionAssistantMessageParamWithReasoning = {
|
||||
role: "assistant",
|
||||
content: compat.requiresAssistantContentString || compat.requiresAssistantAfterToolResult ? "" : null,
|
||||
content: compat.requiresAssistantAfterToolResult ? "" : null,
|
||||
};
|
||||
|
||||
const assistantTextParts = msg.content
|
||||
|
|
@ -1645,7 +1645,6 @@ function detectCompat(model: Model<"openai-completions">): ResolvedOpenAIComplet
|
|||
maxTokensField: useMaxTokens ? "max_tokens" : "max_completion_tokens",
|
||||
requiresToolResultName: false,
|
||||
requiresAssistantAfterToolResult: false,
|
||||
requiresAssistantContentString: false,
|
||||
requiresThinkingAsText: false,
|
||||
requiresReasoningContentOnAssistantMessages: isDeepSeek,
|
||||
thinkingFormat: isDeepSeek
|
||||
|
|
@ -1702,8 +1701,6 @@ function getCompat(model: Model<"openai-completions">): ResolvedOpenAICompletion
|
|||
requiresToolResultName: model.compat.requiresToolResultName ?? detected.requiresToolResultName,
|
||||
requiresAssistantAfterToolResult:
|
||||
model.compat.requiresAssistantAfterToolResult ?? detected.requiresAssistantAfterToolResult,
|
||||
requiresAssistantContentString:
|
||||
model.compat.requiresAssistantContentString ?? detected.requiresAssistantContentString,
|
||||
requiresThinkingAsText: model.compat.requiresThinkingAsText ?? detected.requiresThinkingAsText,
|
||||
requiresReasoningContentOnAssistantMessages:
|
||||
model.compat.requiresReasoningContentOnAssistantMessages ??
|
||||
|
|
|
|||
|
|
@ -232,10 +232,6 @@ const OpenAICompletionsCompatProperties = {
|
|||
"Whether a user message after tool results requires an assistant message in between. Default: auto-detected from URL.",
|
||||
}),
|
||||
),
|
||||
requiresAssistantContentString: optionalCompatBoolean({
|
||||
description: "Whether tool-only assistant messages need an empty string instead of null content.",
|
||||
default: false,
|
||||
}),
|
||||
requiresThinkingAsText: opt(
|
||||
bool({
|
||||
description:
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import { streamSimple } from "../src/api/openai-completions.ts";
|
|||
import { InMemoryCredentialStore } from "../src/auth/credential-store.ts";
|
||||
import { findEnvKeys, getEnvApiKey } from "../src/env-api-keys.ts";
|
||||
import { getSupportedThinkingLevels } from "../src/models.ts";
|
||||
import { builtinModels, getBuiltinModel } from "../src/providers/all.ts";
|
||||
import { builtinModels } from "../src/providers/all.ts";
|
||||
import type { Context, FetchFunction, Model } from "../src/types.ts";
|
||||
import { normalizeContext } from "../src/utils/transcript.ts";
|
||||
|
||||
|
|
@ -230,7 +230,7 @@ describe("LongCat provider", () => {
|
|||
const messages = payload?.messages as Record<string, unknown>[];
|
||||
expect(messages.map((message) => message.role)).toEqual(["system", "user", "assistant", "tool", "user"]);
|
||||
expect(messages[2]).toMatchObject({
|
||||
content: "",
|
||||
content: null,
|
||||
reasoning_content: "Check the weather.",
|
||||
tool_calls: [{ id: "call_weather", function: { name: "weather", arguments: '{"city":"Paris"}' } }],
|
||||
});
|
||||
|
|
@ -274,61 +274,3 @@ describe("LongCat provider", () => {
|
|||
]);
|
||||
});
|
||||
});
|
||||
|
||||
describe("assistant content compatibility", () => {
|
||||
it.each([true, false, undefined])(
|
||||
"uses an empty string only when required (%s)",
|
||||
async (requiresAssistantContentString) => {
|
||||
const base = getBuiltinModel("openai", "gpt-4o");
|
||||
const model: Model<"openai-completions"> = {
|
||||
...base,
|
||||
api: "openai-completions",
|
||||
compat: { requiresAssistantContentString },
|
||||
};
|
||||
const messages: Context = {
|
||||
...context,
|
||||
messages: [
|
||||
...context.messages,
|
||||
{
|
||||
role: "assistant",
|
||||
api: model.api,
|
||||
provider: model.provider,
|
||||
model: model.id,
|
||||
content: [{ type: "toolCall", id: "call_weather", name: "weather", arguments: { city: "Paris" } }],
|
||||
usage: {
|
||||
input: 0,
|
||||
output: 0,
|
||||
cacheRead: 0,
|
||||
cacheWrite: 0,
|
||||
totalTokens: 0,
|
||||
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 },
|
||||
},
|
||||
stopReason: "toolUse",
|
||||
timestamp: 1,
|
||||
},
|
||||
{
|
||||
role: "toolResult",
|
||||
toolCallId: "call_weather",
|
||||
toolName: "weather",
|
||||
content: [{ type: "text", text: "Sunny" }],
|
||||
isError: false,
|
||||
timestamp: 2,
|
||||
},
|
||||
],
|
||||
};
|
||||
let payload: Record<string, unknown> | undefined;
|
||||
const result = await streamSimple(model, normalizeContext(messages), {
|
||||
apiKey: "test-key",
|
||||
fetch: async (input, init) => {
|
||||
payload = (await new Request(input, init).json()) as Record<string, unknown>;
|
||||
return sse([chunk({ content: "ok" }, "stop")]);
|
||||
},
|
||||
}).result();
|
||||
expect(result.stopReason).toBe("stop");
|
||||
const assistant = (payload?.messages as Record<string, unknown>[]).find(
|
||||
(message) => message.role === "assistant",
|
||||
);
|
||||
expect(assistant?.content).toBe(requiresAssistantContentString ? "" : null);
|
||||
},
|
||||
);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -31,7 +31,6 @@ const compat = {
|
|||
maxTokensField: "max_completion_tokens",
|
||||
requiresToolResultName: false,
|
||||
requiresAssistantAfterToolResult: false,
|
||||
requiresAssistantContentString: false,
|
||||
requiresThinkingAsText: true,
|
||||
requiresReasoningContentOnAssistantMessages: false,
|
||||
thinkingFormat: "openai",
|
||||
|
|
|
|||
|
|
@ -1405,7 +1405,6 @@ describe("openai-completions tool_choice", () => {
|
|||
maxTokensField: "max_completion_tokens",
|
||||
requiresToolResultName: false,
|
||||
requiresAssistantAfterToolResult: false,
|
||||
requiresAssistantContentString: false,
|
||||
requiresThinkingAsText: false,
|
||||
requiresReasoningContentOnAssistantMessages: false,
|
||||
thinkingFormat: "openai",
|
||||
|
|
|
|||
|
|
@ -23,7 +23,6 @@ const compat: Omit<Required<OpenAICompletionsCompat>, "thinkingTokenBudgetField"
|
|||
maxTokensField: "max_completion_tokens",
|
||||
requiresToolResultName: false,
|
||||
requiresAssistantAfterToolResult: false,
|
||||
requiresAssistantContentString: false,
|
||||
requiresThinkingAsText: false,
|
||||
requiresReasoningContentOnAssistantMessages: false,
|
||||
thinkingFormat: "openai",
|
||||
|
|
|
|||
|
|
@ -118,7 +118,6 @@ See [Describe model input and caching](models.md#describe-model-input-and-cachin
|
|||
| `maxTokensField` | `"max_completion_tokens" \| "max_tokens"` | | Which field to use for max tokens. Default: auto-detected from URL. |
|
||||
| `requiresToolResultName` | boolean | | Whether tool results require the name field. Default: auto-detected from URL. |
|
||||
| `requiresAssistantAfterToolResult` | boolean | | Whether a user message after tool results requires an assistant message in between. Default: auto-detected from URL. |
|
||||
| `requiresAssistantContentString` | boolean | `false` | Whether tool-only assistant messages need an empty string instead of null content. |
|
||||
| `requiresThinkingAsText` | boolean | | Whether thinking blocks must be converted to text blocks with <thinking> delimiters. Default: auto-detected from URL. |
|
||||
| `requiresReasoningContentOnAssistantMessages` | boolean | | Whether all replayed assistant messages must include an empty reasoning_content field when reasoning is enabled. Default: auto-detected from URL. |
|
||||
| `thinkingFormat` | `"openai" \| "openrouter" \| "deepseek" \| "together" \| "baseten" \| "zai" \| "qwen" \| "chat-template" \| "qwen-chat-template" \| "string-thinking" \| "ant-ling"` | | Format for reasoning or thinking parameters. When omitted, Pi auto-detects the format from the provider URL. "openai" uses reasoning_effort, "openrouter" uses reasoning.effort, "deepseek" uses thinking.type plus reasoning_effort when supported, "together" uses reasoning.enabled plus reasoning_effort when supported, "baseten" uses configurable chat_template_args plus reasoning_effort when supported, "zai" uses thinking.type, "qwen" uses top-level enable_thinking, "qwen-chat-template" uses chat_template_kwargs.enable_thinking and preserve_thinking, "chat-template" uses configurable chat_template_kwargs, "string-thinking" uses top-level thinking, and "ant-ling" uses reasoning.effort only when the mapped effort is non-null. |
|
||||
|
|
|
|||
|
|
@ -552,11 +552,6 @@
|
|||
"type": "boolean",
|
||||
"description": "Whether a user message after tool results requires an assistant message in between. Default: auto-detected from URL."
|
||||
},
|
||||
"requiresAssistantContentString": {
|
||||
"type": "boolean",
|
||||
"description": "Whether tool-only assistant messages need an empty string instead of null content.",
|
||||
"default": false
|
||||
},
|
||||
"requiresThinkingAsText": {
|
||||
"type": "boolean",
|
||||
"description": "Whether thinking blocks must be converted to text blocks with <thinking> delimiters. Default: auto-detected from URL."
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue