mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-10 05:32:44 +00:00
fix: make tool schema normalization provider-aware
The cleanSchemaForGemini function was being applied universally to all tools for all providers, stripping out valid JSON Schema keywords like minimum/maximum that are required by Anthropic's draft 2020-12 validation. This caused the 21st tool (web_search) to fail with google-antigravity because its count parameter's constraints were being removed. Changes: - Modified normalizeToolParameters to accept modelProvider option - Only apply Gemini-specific cleaning when provider is Gemini/Google - Skip aggressive cleaning for Anthropic/google-antigravity providers - Updated call site in createOpenClawCodingTools to pass modelProvider Fixes schema validation errors for Anthropic models served via google-antigravity. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
committed by
Peter Steinberger
parent
382158fb30
commit
fe94e83f6b
@@ -62,7 +62,10 @@ function mergePropertySchemas(existing: unknown, incoming: unknown): unknown {
|
|||||||
return existing;
|
return existing;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function normalizeToolParameters(tool: AnyAgentTool): AnyAgentTool {
|
export function normalizeToolParameters(
|
||||||
|
tool: AnyAgentTool,
|
||||||
|
options?: { modelProvider?: string },
|
||||||
|
): AnyAgentTool {
|
||||||
const schema =
|
const schema =
|
||||||
tool.parameters && typeof tool.parameters === "object"
|
tool.parameters && typeof tool.parameters === "object"
|
||||||
? (tool.parameters as Record<string, unknown>)
|
? (tool.parameters as Record<string, unknown>)
|
||||||
@@ -75,15 +78,23 @@ export function normalizeToolParameters(tool: AnyAgentTool): AnyAgentTool {
|
|||||||
// - Gemini rejects several JSON Schema keywords, so we scrub those.
|
// - Gemini rejects several JSON Schema keywords, so we scrub those.
|
||||||
// - OpenAI rejects function tool schemas unless the *top-level* is `type: "object"`.
|
// - OpenAI rejects function tool schemas unless the *top-level* is `type: "object"`.
|
||||||
// (TypeBox root unions compile to `{ anyOf: [...] }` without `type`).
|
// (TypeBox root unions compile to `{ anyOf: [...] }` without `type`).
|
||||||
|
// - Anthropic (google-antigravity) expects full JSON Schema draft 2020-12 compliance.
|
||||||
//
|
//
|
||||||
// Normalize once here so callers can always pass `tools` through unchanged.
|
// Normalize once here so callers can always pass `tools` through unchanged.
|
||||||
|
|
||||||
|
const isGeminiProvider =
|
||||||
|
options?.modelProvider?.toLowerCase().includes("google") ||
|
||||||
|
options?.modelProvider?.toLowerCase().includes("gemini");
|
||||||
|
const isAnthropicProvider =
|
||||||
|
options?.modelProvider?.toLowerCase().includes("anthropic") ||
|
||||||
|
options?.modelProvider?.toLowerCase().includes("google-antigravity");
|
||||||
|
|
||||||
// If schema already has type + properties (no top-level anyOf to merge),
|
// If schema already has type + properties (no top-level anyOf to merge),
|
||||||
// still clean it for Gemini compatibility
|
// clean it for Gemini compatibility (but only if using Gemini, not Anthropic)
|
||||||
if ("type" in schema && "properties" in schema && !Array.isArray(schema.anyOf)) {
|
if ("type" in schema && "properties" in schema && !Array.isArray(schema.anyOf)) {
|
||||||
return {
|
return {
|
||||||
...tool,
|
...tool,
|
||||||
parameters: cleanSchemaForGemini(schema),
|
parameters: isGeminiProvider && !isAnthropicProvider ? cleanSchemaForGemini(schema) : schema,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -95,9 +106,13 @@ export function normalizeToolParameters(tool: AnyAgentTool): AnyAgentTool {
|
|||||||
!Array.isArray(schema.anyOf) &&
|
!Array.isArray(schema.anyOf) &&
|
||||||
!Array.isArray(schema.oneOf)
|
!Array.isArray(schema.oneOf)
|
||||||
) {
|
) {
|
||||||
|
const schemaWithType = { ...schema, type: "object" };
|
||||||
return {
|
return {
|
||||||
...tool,
|
...tool,
|
||||||
parameters: cleanSchemaForGemini({ ...schema, type: "object" }),
|
parameters:
|
||||||
|
isGeminiProvider && !isAnthropicProvider
|
||||||
|
? cleanSchemaForGemini(schemaWithType)
|
||||||
|
: schemaWithType,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -154,26 +169,34 @@ export function normalizeToolParameters(tool: AnyAgentTool): AnyAgentTool {
|
|||||||
: undefined;
|
: undefined;
|
||||||
|
|
||||||
const nextSchema: Record<string, unknown> = { ...schema };
|
const nextSchema: Record<string, unknown> = { ...schema };
|
||||||
|
const flattenedSchema = {
|
||||||
|
type: "object",
|
||||||
|
...(typeof nextSchema.title === "string" ? { title: nextSchema.title } : {}),
|
||||||
|
...(typeof nextSchema.description === "string" ? { description: nextSchema.description } : {}),
|
||||||
|
properties:
|
||||||
|
Object.keys(mergedProperties).length > 0 ? mergedProperties : (schema.properties ?? {}),
|
||||||
|
...(mergedRequired && mergedRequired.length > 0 ? { required: mergedRequired } : {}),
|
||||||
|
additionalProperties: "additionalProperties" in schema ? schema.additionalProperties : true,
|
||||||
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...tool,
|
...tool,
|
||||||
// Flatten union schemas into a single object schema:
|
// Flatten union schemas into a single object schema:
|
||||||
// - Gemini doesn't allow top-level `type` together with `anyOf`.
|
// - Gemini doesn't allow top-level `type` together with `anyOf`.
|
||||||
// - OpenAI rejects schemas without top-level `type: "object"`.
|
// - OpenAI rejects schemas without top-level `type: "object"`.
|
||||||
|
// - Anthropic accepts proper JSON Schema with constraints.
|
||||||
// Merging properties preserves useful enums like `action` while keeping schemas portable.
|
// Merging properties preserves useful enums like `action` while keeping schemas portable.
|
||||||
parameters: cleanSchemaForGemini({
|
parameters:
|
||||||
type: "object",
|
isGeminiProvider && !isAnthropicProvider
|
||||||
...(typeof nextSchema.title === "string" ? { title: nextSchema.title } : {}),
|
? cleanSchemaForGemini(flattenedSchema)
|
||||||
...(typeof nextSchema.description === "string"
|
: flattenedSchema,
|
||||||
? { description: nextSchema.description }
|
|
||||||
: {}),
|
|
||||||
properties:
|
|
||||||
Object.keys(mergedProperties).length > 0 ? mergedProperties : (schema.properties ?? {}),
|
|
||||||
...(mergedRequired && mergedRequired.length > 0 ? { required: mergedRequired } : {}),
|
|
||||||
additionalProperties: "additionalProperties" in schema ? schema.additionalProperties : true,
|
|
||||||
}),
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @deprecated Use normalizeToolParameters with modelProvider instead.
|
||||||
|
* This function should only be used for Gemini providers.
|
||||||
|
*/
|
||||||
export function cleanToolSchemaForGemini(schema: Record<string, unknown>): unknown {
|
export function cleanToolSchemaForGemini(schema: Record<string, unknown>): unknown {
|
||||||
return cleanSchemaForGemini(schema);
|
return cleanSchemaForGemini(schema);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -446,7 +446,10 @@ export function createOpenClawCodingTools(options?: {
|
|||||||
});
|
});
|
||||||
// Always normalize tool JSON Schemas before handing them to pi-agent/pi-ai.
|
// Always normalize tool JSON Schemas before handing them to pi-agent/pi-ai.
|
||||||
// Without this, some providers (notably OpenAI) will reject root-level union schemas.
|
// Without this, some providers (notably OpenAI) will reject root-level union schemas.
|
||||||
const normalized = subagentFiltered.map(normalizeToolParameters);
|
// Provider-specific cleaning: Gemini needs constraint keywords stripped, but Anthropic expects them.
|
||||||
|
const normalized = subagentFiltered.map((tool) =>
|
||||||
|
normalizeToolParameters(tool, { modelProvider: options?.modelProvider }),
|
||||||
|
);
|
||||||
const withHooks = normalized.map((tool) =>
|
const withHooks = normalized.map((tool) =>
|
||||||
wrapToolWithBeforeToolCallHook(tool, {
|
wrapToolWithBeforeToolCallHook(tool, {
|
||||||
agentId,
|
agentId,
|
||||||
|
|||||||
Reference in New Issue
Block a user