fix(ai): preserved multi-type ollama tool unions
- Kept nullable single-type arrays flattened for Ollama while expanding true multi-type arrays into anyOf alternatives. - Covered non-null string/number and object/array unions in the Ollama provider payload regression test. Refs #4488
This commit is contained in:
@@ -1086,6 +1086,7 @@ export function sanitizeSchemaForOllama(schema: JsonObject): JsonObject {
|
||||
|
||||
let changed = false;
|
||||
const output: JsonObject = {};
|
||||
let typeAlternatives: JsonObject[] | undefined;
|
||||
for (const key in value) {
|
||||
if (!Object.hasOwn(value, key)) continue;
|
||||
const child = value[key];
|
||||
@@ -1095,8 +1096,13 @@ export function sanitizeSchemaForOllama(schema: JsonObject): JsonObject {
|
||||
}
|
||||
if (key === "type" && Array.isArray(child)) {
|
||||
const variants = child.filter((entry): entry is string => typeof entry === "string");
|
||||
const nonNull = variants.filter(entry => entry !== "null");
|
||||
output.type = nonNull[0] ?? variants[0] ?? child[0];
|
||||
const uniqueVariants = [...new Set(variants)];
|
||||
const nonNull = uniqueVariants.filter(entry => entry !== "null");
|
||||
if (nonNull.length <= 1) {
|
||||
output.type = nonNull[0] ?? uniqueVariants[0] ?? child[0];
|
||||
} else {
|
||||
typeAlternatives = uniqueVariants.map(entry => ({ type: entry }));
|
||||
}
|
||||
changed = true;
|
||||
continue;
|
||||
}
|
||||
@@ -1128,6 +1134,11 @@ export function sanitizeSchemaForOllama(schema: JsonObject): JsonObject {
|
||||
output[key] = next;
|
||||
}
|
||||
|
||||
if (typeAlternatives) {
|
||||
const existingAnyOf = output.anyOf;
|
||||
output.anyOf = Array.isArray(existingAnyOf) ? [...typeAlternatives, ...existingAnyOf] : typeAlternatives;
|
||||
}
|
||||
|
||||
return changed ? output : value;
|
||||
};
|
||||
return normalizeNode(schema) as JsonObject;
|
||||
|
||||
@@ -101,6 +101,8 @@ describe("Ollama chat thinking controls", () => {
|
||||
properties: {
|
||||
anything: {},
|
||||
nullableName: { type: ["string", "null"] },
|
||||
stringOrNumber: { type: ["string", "number"] },
|
||||
objectOrArray: { type: ["object", "array"] },
|
||||
list: { type: "array", items: {} },
|
||||
union: { anyOf: [{}, { type: "string" }] },
|
||||
nested: {
|
||||
@@ -109,7 +111,7 @@ describe("Ollama chat thinking controls", () => {
|
||||
additionalProperties: false,
|
||||
},
|
||||
},
|
||||
required: ["anything", "nullableName", "list", "union", "nested"],
|
||||
required: ["anything", "nullableName", "stringOrNumber", "objectOrArray", "list", "union", "nested"],
|
||||
additionalProperties: false,
|
||||
},
|
||||
};
|
||||
@@ -143,6 +145,10 @@ describe("Ollama chat thinking controls", () => {
|
||||
expect(Object.hasOwn(parameters, "additionalProperties")).toBe(false);
|
||||
expect(properties.anything).toEqual(widenedOpen);
|
||||
expect(properties.nullableName?.type).toBe("string");
|
||||
expect(properties.stringOrNumber?.anyOf).toEqual([{ type: "string" }, { type: "number" }]);
|
||||
expect(Object.hasOwn(properties.stringOrNumber, "type")).toBe(false);
|
||||
expect(properties.objectOrArray?.anyOf).toEqual([{ type: "object" }, { type: "array" }]);
|
||||
expect(Object.hasOwn(properties.objectOrArray, "type")).toBe(false);
|
||||
expect(properties.list?.items).toEqual(widenedOpen);
|
||||
expect(properties.union?.anyOf).toEqual([widenedOpen, { type: "string" }]);
|
||||
expect(Object.hasOwn(properties.nested, "additionalProperties")).toBe(false);
|
||||
|
||||
Reference in New Issue
Block a user