Skip to content

Commit 38ff1b5

Browse files
authored
fix: add support for anyOf, allOf, oneOf in openapi conversion (#8964)
1 parent dc4e3f8 commit 38ff1b5

File tree

3 files changed

+254
-0
lines changed

3 files changed

+254
-0
lines changed

.changeset/petite-lights-doubt.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"langchain": patch
3+
---
4+
5+
add support for anyOf, allOf, oneOf in openapi conversion

libs/langchain-classic/src/chains/openai_functions/openapi.ts

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,31 @@ type OpenAPIExecutionMethod = (
3434
}
3535
) => Promise<string>;
3636

37+
/**
38+
* Type representing the composition types of a schema.
39+
*/
40+
type CompositionType = "anyOf" | "allOf" | "oneOf";
41+
42+
/**
43+
* Gets the composition type of a schema if it exists.
44+
* @param schema
45+
* @returns The composition type of the schema if it exists.
46+
*/
47+
function getCompositionType(
48+
schema: OpenAPIV3_1.SchemaObject
49+
): CompositionType | undefined {
50+
if (schema.anyOf !== undefined) {
51+
return "anyOf";
52+
}
53+
if (schema.allOf !== undefined) {
54+
return "allOf";
55+
}
56+
if (schema.oneOf !== undefined) {
57+
return "oneOf";
58+
}
59+
return undefined;
60+
}
61+
3762
/**
3863
* Formats a URL by replacing path parameters with their corresponding
3964
* values.
@@ -157,6 +182,15 @@ export function convertOpenAPISchemaToJSONSchema(
157182
schema: OpenAPIV3_1.SchemaObject,
158183
spec: OpenAPISpec
159184
): JsonSchema7Type {
185+
const compositionType = getCompositionType(schema);
186+
if (compositionType !== undefined && schema[compositionType] !== undefined) {
187+
return {
188+
[compositionType]: schema[compositionType].map((s) =>
189+
convertOpenAPISchemaToJSONSchema(spec.getSchema(s), spec)
190+
),
191+
} as JsonSchema7Type;
192+
}
193+
160194
if (schema.type === "object") {
161195
return Object.keys(schema.properties ?? {}).reduce(
162196
(jsonSchema: JsonSchema7ObjectType, propertyName) => {
@@ -187,6 +221,7 @@ export function convertOpenAPISchemaToJSONSchema(
187221
}
188222
);
189223
}
224+
190225
if (schema.type === "array") {
191226
const openAPIItems = spec.getSchema(schema.items ?? {});
192227
return {

libs/langchain-classic/src/chains/openai_functions/tests/openapi.test.ts

Lines changed: 214 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,104 @@ test("Test convert OpenAPI params to JSON Schema", async () => {
142142
},
143143
},
144144
},
145+
{
146+
name: "anyOfParam",
147+
in: "query",
148+
schema: {
149+
anyOf: [
150+
{ $ref: "#/components/schemas/RefObject" },
151+
{ type: "number" },
152+
],
153+
},
154+
},
155+
{
156+
name: "anyOfArrayParam",
157+
in: "query",
158+
schema: {
159+
type: "array",
160+
items: {
161+
anyOf: [
162+
{ $ref: "#/components/schemas/RefObject" },
163+
{ type: "string" },
164+
],
165+
},
166+
},
167+
},
168+
{
169+
name: "allOfParam",
170+
in: "query",
171+
schema: {
172+
allOf: [
173+
{ $ref: "#/components/schemas/RefObject" },
174+
{
175+
type: "object",
176+
properties: {
177+
baz: {
178+
type: "string",
179+
},
180+
},
181+
},
182+
],
183+
},
184+
},
185+
{
186+
name: "allOfArrayParam",
187+
in: "query",
188+
schema: {
189+
type: "array",
190+
items: {
191+
allOf: [
192+
{
193+
type: "object",
194+
properties: {
195+
foo: {
196+
type: "number",
197+
},
198+
},
199+
},
200+
{
201+
type: "object",
202+
properties: {
203+
bar: {
204+
type: "string",
205+
},
206+
},
207+
},
208+
],
209+
},
210+
},
211+
},
212+
{
213+
name: "oneOfParam",
214+
in: "query",
215+
schema: {
216+
oneOf: [
217+
{ type: "string" },
218+
{ type: "number" },
219+
{ $ref: "#/components/schemas/RefObject" },
220+
],
221+
},
222+
},
223+
{
224+
name: "oneOfArrayParam",
225+
in: "query",
226+
schema: {
227+
type: "array",
228+
items: {
229+
oneOf: [
230+
{
231+
type: "string",
232+
},
233+
{
234+
type: "array",
235+
items: {
236+
type: "string",
237+
},
238+
},
239+
],
240+
},
241+
},
242+
},
145243
],
146244
responses: {
147245
"200": {
@@ -224,6 +322,25 @@ test("Test convert OpenAPI params to JSON Schema", async () => {
224322
return schema as TypeMap[T];
225323
}
226324

325+
function expectComposition(
326+
compositionType: "anyOf" | "allOf" | "oneOf",
327+
schema: JsonSchema7Type | undefined
328+
): {
329+
[compositionType]: JsonSchema7Type[];
330+
} {
331+
if (
332+
!schema ||
333+
Object.keys(schema).length !== 1 ||
334+
!(compositionType in schema)
335+
) {
336+
throw new Error(`Schema has no ${compositionType}`);
337+
}
338+
339+
return schema as {
340+
[compositionType]: JsonSchema7Type[];
341+
};
342+
}
343+
227344
const stringParamSchema = convertOpenAPISchemaToJSONSchema(
228345
getParamSchema(createWidget, "stringParam"),
229346
spec
@@ -317,6 +434,103 @@ test("Test convert OpenAPI params to JSON Schema", async () => {
317434
).properties.nestedObject
318435
).properties.inception
319436
);
437+
438+
const anyOfParamSchema = convertOpenAPISchemaToJSONSchema(
439+
getParamSchema(createWidget, "anyOfParam"),
440+
spec
441+
);
442+
const typedAnyOfParamSchema = expectComposition("anyOf", anyOfParamSchema);
443+
const typedAnyOfParamSchemaItem1 = expectType(
444+
"object",
445+
typedAnyOfParamSchema.anyOf[0]
446+
);
447+
expectType("string", typedAnyOfParamSchemaItem1.properties.foo);
448+
expectType("number", typedAnyOfParamSchemaItem1.properties.bar);
449+
expectType("number", typedAnyOfParamSchema.anyOf[1]);
450+
451+
const anyOfArrayParamSchema = convertOpenAPISchemaToJSONSchema(
452+
getParamSchema(createWidget, "anyOfArrayParam"),
453+
spec
454+
);
455+
const typedAnyOfArrayParamSchema = expectType("array", anyOfArrayParamSchema);
456+
const typedAnyOfArrayParamSchemaItems = expectComposition(
457+
"anyOf",
458+
typedAnyOfArrayParamSchema.items
459+
);
460+
const typedAnyOfArrayParamSchemaItem1 = expectType(
461+
"object",
462+
typedAnyOfArrayParamSchemaItems.anyOf[0]
463+
);
464+
expectType("string", typedAnyOfArrayParamSchemaItem1.properties.foo);
465+
expectType("number", typedAnyOfArrayParamSchemaItem1.properties.bar);
466+
expectType("string", typedAnyOfArrayParamSchemaItems.anyOf[1]);
467+
468+
const allOfParamSchema = convertOpenAPISchemaToJSONSchema(
469+
getParamSchema(createWidget, "allOfParam"),
470+
spec
471+
);
472+
const typedAllOfParamSchema = expectComposition("allOf", allOfParamSchema);
473+
const typedAllOfParamSchemaItem1 = expectType(
474+
"object",
475+
typedAllOfParamSchema.allOf[0]
476+
);
477+
expectType("string", typedAllOfParamSchemaItem1.properties.foo);
478+
expectType("number", typedAllOfParamSchemaItem1.properties.bar);
479+
const typedAllOfParamSchemaItem2 = expectType(
480+
"object",
481+
typedAllOfParamSchema.allOf[1]
482+
);
483+
expectType("string", typedAllOfParamSchemaItem2.properties.baz);
484+
485+
const oneOfParamSchema = convertOpenAPISchemaToJSONSchema(
486+
getParamSchema(createWidget, "oneOfParam"),
487+
spec
488+
);
489+
const typedOneOfParamSchema = expectComposition("oneOf", oneOfParamSchema);
490+
expectType("string", typedOneOfParamSchema.oneOf[0]);
491+
expectType("number", typedOneOfParamSchema.oneOf[1]);
492+
const typedOneOfParamSchemaItem3 = expectType(
493+
"object",
494+
typedOneOfParamSchema.oneOf[2]
495+
);
496+
expectType("string", typedOneOfParamSchemaItem3.properties.foo);
497+
expectType("number", typedOneOfParamSchemaItem3.properties.bar);
498+
499+
const allOfArrayParamSchema = convertOpenAPISchemaToJSONSchema(
500+
getParamSchema(createWidget, "allOfArrayParam"),
501+
spec
502+
);
503+
const typedAllOfArrayParamSchema = expectType("array", allOfArrayParamSchema);
504+
const typedAllOfArrayParamSchemaItems = expectComposition(
505+
"allOf",
506+
typedAllOfArrayParamSchema.items
507+
);
508+
const typedAllOfArrayParamSchemaItem1 = expectType(
509+
"object",
510+
typedAllOfArrayParamSchemaItems.allOf[0]
511+
);
512+
expectType("number", typedAllOfArrayParamSchemaItem1.properties.foo);
513+
const typedAllOfArrayParamSchemaItem2 = expectType(
514+
"object",
515+
typedAllOfArrayParamSchemaItems.allOf[1]
516+
);
517+
expectType("string", typedAllOfArrayParamSchemaItem2.properties.bar);
518+
519+
const oneOfArrayParamSchema = convertOpenAPISchemaToJSONSchema(
520+
getParamSchema(createWidget, "oneOfArrayParam"),
521+
spec
522+
);
523+
const typedOneOfArrayParamSchema = expectType("array", oneOfArrayParamSchema);
524+
const typedOneOfArrayParamSchemaItems = expectComposition(
525+
"oneOf",
526+
typedOneOfArrayParamSchema.items
527+
);
528+
expectType("string", typedOneOfArrayParamSchemaItems.oneOf[0]);
529+
const typedOneOfArrayParamSchemaItem2 = expectType(
530+
"array",
531+
typedOneOfArrayParamSchemaItems.oneOf[1]
532+
);
533+
expectType("string", typedOneOfArrayParamSchemaItem2.items);
320534
});
321535

322536
test("Parent required should not include child due to child's internal required", async () => {

0 commit comments

Comments
 (0)