feat: enhance OpenAI compatibility for tool calling
This commit is contained in:
parent
4583f88626
commit
bb4d6fb610
@ -2336,6 +2336,11 @@ Select the model via `agents.defaults.model.primary` (provider/model).
|
||||
}
|
||||
```
|
||||
|
||||
Tool calling note (OpenAI-compatible servers):
|
||||
- Some servers only support tool calling via `POST /v1/chat/completions` when the client sends
|
||||
`tools` + `tool_choice`. If your `openai-completions` model never emits tool calls, set
|
||||
`compat: { openaiCompletionsTools: true }` on that model entry to force Moltbot to include tools.
|
||||
|
||||
### OpenCode Zen (multi-model proxy)
|
||||
|
||||
OpenCode Zen is a multi-model gateway with per-model endpoints. Moltbot uses
|
||||
|
||||
@ -120,6 +120,7 @@ describe("splitSdkTools", () => {
|
||||
const { builtInTools, customTools } = splitSdkTools({
|
||||
tools,
|
||||
sandboxEnabled: true,
|
||||
modelApi: "openai-responses",
|
||||
});
|
||||
expect(builtInTools).toEqual([]);
|
||||
expect(customTools.map((tool) => tool.name)).toEqual([
|
||||
@ -134,6 +135,7 @@ describe("splitSdkTools", () => {
|
||||
const { builtInTools, customTools } = splitSdkTools({
|
||||
tools,
|
||||
sandboxEnabled: false,
|
||||
modelApi: "openai-responses",
|
||||
});
|
||||
expect(builtInTools).toEqual([]);
|
||||
expect(customTools.map((tool) => tool.name)).toEqual([
|
||||
@ -144,4 +146,21 @@ describe("splitSdkTools", () => {
|
||||
"browser",
|
||||
]);
|
||||
});
|
||||
|
||||
it("can route tools to builtInTools for openai-completions when enabled", () => {
|
||||
const { builtInTools, customTools } = splitSdkTools({
|
||||
tools,
|
||||
sandboxEnabled: false,
|
||||
modelApi: "openai-completions",
|
||||
openaiCompletionsTools: true,
|
||||
});
|
||||
expect(builtInTools.map((tool) => tool.name)).toEqual([
|
||||
"read",
|
||||
"exec",
|
||||
"edit",
|
||||
"write",
|
||||
"browser",
|
||||
]);
|
||||
expect(customTools).toEqual([]);
|
||||
});
|
||||
});
|
||||
|
||||
@ -378,9 +378,16 @@ export async function compactEmbeddedPiSessionDirect(
|
||||
model,
|
||||
});
|
||||
|
||||
const openaiCompletionsTools =
|
||||
model.api === "openai-completions" &&
|
||||
(model.compat as { openaiCompletionsTools?: unknown } | undefined)
|
||||
?.openaiCompletionsTools === true;
|
||||
|
||||
const { builtInTools, customTools } = splitSdkTools({
|
||||
tools,
|
||||
sandboxEnabled: !!sandbox?.enabled,
|
||||
modelApi: model.api,
|
||||
openaiCompletionsTools,
|
||||
});
|
||||
|
||||
let session: Awaited<ReturnType<typeof createAgentSession>>["session"];
|
||||
|
||||
@ -432,9 +432,16 @@ export async function runEmbeddedAttempt(
|
||||
model: params.model,
|
||||
});
|
||||
|
||||
const openaiCompletionsTools =
|
||||
params.model.api === "openai-completions" &&
|
||||
(params.model.compat as { openaiCompletionsTools?: unknown } | undefined)
|
||||
?.openaiCompletionsTools === true;
|
||||
|
||||
const { builtInTools, customTools } = splitSdkTools({
|
||||
tools,
|
||||
sandboxEnabled: !!sandbox?.enabled,
|
||||
modelApi: params.model.api,
|
||||
openaiCompletionsTools,
|
||||
});
|
||||
|
||||
// Add client tools (OpenResponses hosted tools) to customTools
|
||||
|
||||
@ -2,15 +2,36 @@ import type { AgentTool } from "@mariozechner/pi-agent-core";
|
||||
|
||||
import { toToolDefinitions } from "../pi-tool-definition-adapter.js";
|
||||
|
||||
// We always pass tools via `customTools` so our policy filtering, sandbox integration,
|
||||
// and extended toolset remain consistent across providers.
|
||||
type AnyAgentTool = AgentTool;
|
||||
|
||||
export function splitSdkTools(options: { tools: AnyAgentTool[]; sandboxEnabled: boolean }): {
|
||||
function isOpenAiCompletionsToolsEnabled(options: {
|
||||
modelApi?: string;
|
||||
openaiCompletionsTools?: boolean;
|
||||
}): boolean {
|
||||
return options.modelApi === "openai-completions" && options.openaiCompletionsTools === true;
|
||||
}
|
||||
|
||||
export function splitSdkTools(options: {
|
||||
tools: AnyAgentTool[];
|
||||
sandboxEnabled: boolean;
|
||||
modelApi?: string;
|
||||
openaiCompletionsTools?: boolean;
|
||||
}): {
|
||||
builtInTools: AnyAgentTool[];
|
||||
customTools: ReturnType<typeof toToolDefinitions>;
|
||||
} {
|
||||
const { tools } = options;
|
||||
// Default behavior: route all tools through `customTools` so our policy filtering,
|
||||
// sandbox integration, and extended toolset remain consistent across providers.
|
||||
//
|
||||
// Some OpenAI-compatible servers (notably local vLLM) only support tool calling on
|
||||
// /v1/chat/completions when tools are passed via the SDK tool path.
|
||||
if (isOpenAiCompletionsToolsEnabled(options)) {
|
||||
return {
|
||||
builtInTools: tools,
|
||||
customTools: [],
|
||||
};
|
||||
}
|
||||
return {
|
||||
builtInTools: [],
|
||||
customTools: toToolDefinitions(tools),
|
||||
|
||||
@ -11,6 +11,14 @@ export type ModelCompatConfig = {
|
||||
supportsDeveloperRole?: boolean;
|
||||
supportsReasoningEffort?: boolean;
|
||||
maxTokensField?: "max_completion_tokens" | "max_tokens";
|
||||
/**
|
||||
* For some OpenAI-compatible servers (e.g. local vLLM), tool calling is only
|
||||
* supported via `POST /v1/chat/completions` with `tools` + `tool_choice`.
|
||||
*
|
||||
* When enabled for an `openai-completions` model, Moltbot routes the agent's
|
||||
* toolset through the SDK tool path so tools are included in the request.
|
||||
*/
|
||||
openaiCompletionsTools?: boolean;
|
||||
};
|
||||
|
||||
export type ModelProviderAuthMode = "api-key" | "aws-sdk" | "oauth" | "token";
|
||||
|
||||
@ -19,6 +19,7 @@ export const ModelCompatSchema = z
|
||||
maxTokensField: z
|
||||
.union([z.literal("max_completion_tokens"), z.literal("max_tokens")])
|
||||
.optional(),
|
||||
openaiCompletionsTools: z.boolean().optional(),
|
||||
})
|
||||
.strict()
|
||||
.optional();
|
||||
|
||||
Loading…
Reference in New Issue
Block a user