langchainjs
langchainjs copied to clipboard
feat/convert-to-openai-function
-
Main Idea
-
Examples
-
Arrays
- Parameters
- Exceptions
- Example
- Options
-
Strings
- Parameters
- Exceptions
- Example
- Options
-
Boolean
- Parameters
- Example
- Options
-
Enum
- Parameters
- Example
- Options
-
Null
- Parameters
- Example
- Options
-
Number
- Parameters
- Example
- Options
-
Object
- Parameters
- Example
- Options
-
Optional
- Parameters
- Example
-
Function Call
- Parameters
- Example
- Options
-
Arrays
-
Examples
This commit introduces a new file convert-to-openai-function.ts
to the project. It primarily contains decorator functions and related utility functions. These decorators are used to generate JSON schema markup for OpenAI properties, such as String, Number, Array, Enum, and etc. Additionally, helper functions are created to facilitate such conversions and other operations.
Main Idea
This is a similar implementation to the convert_to_open_ai_functioon
from Python, with some added features. It follows the JSON Schema architecture, and down to the bone, it is a class to JSON Schema
converter.
Examples
This section will show examples of how to use the decorators:
Arrays
export const AiArray =
(description: string, type: any[], options?: PropertyOptions<ArraySchema>): PropertyDecorator =>
(target: object, propertyKey: string | symbol) => {
...
};
The AiArray function is used to define an array type property along with its properties such as type, title, and items. It returns a PropertyDecorator function that can be used to decorate properties of an target object.
Parameters
- description (string): Description for the property. This field is used to describe the intention or usage of the property it decorates.
- type (any[]): An array describing the types of the items in the array property. The array should contain only one type. Tuples are not supported.
- options (_ PropertyOptions _, optional): Options object where you can set extra parameters for your property. If not specified, the default values will be used.
Exceptions
- Throws an error if the provided types array is empty. At least one type should be provided for the array.
- Throws an error if the types array contains more than one type as tuples are not supported in this case.
Example
class MyClass {
@AiArray('This is my array property', [String])
property: string[];
}
Options
PropertyOptions<ArraySchema> is a TypeScript type representing an object structure. Fields of PropertyOptions<ArraySchema>:
- type ('array'): This is an intrinsic type of the property. For AiArray, this is always 'array'.
- title (string): The title of the schema that describes the property.
- items (object): An object describing the type of the items in the array. It has a type property which should be either 'object' with properties field or one of the following values: 'string', 'boolean', 'number', 'array'.
- description (string): The description of the schema that describes the property.
Strings
export const AiString =
(description: string, options?: PropertyOptions<StringSchema>): PropertyDecorator =>
(target: object, propertyKey: string | symbol) => {
...
};
The AiString function is used to define a string type property and its related properties such as description, pattern, minimum length, and maximum length. It returns a PropertyDecorator function that can be used to decorate properties of an target object.
Parameters
- description (string): Description for the property. This field is used to describe the intention or usage of the property it decorates.
- options (PropertyOptions , optional): Options object, where you can set extra parameters for your property. If not specified, the default values will be used. See the Structure of PropertyOptions<StringSchema> section for information about the properties available in this parameter.
Exceptions
- Throws an error if the minLength value is less than 0.
- Throws an error if the minLength value is greater than or equal to the maxLength value.
Example
class MyClass {
@AiString('This is my string property')
property: string;
}
Options
PropertyOptions<StringSchema> is a TypeScript type representing an object structure. Fields of PropertyOptions<StringSchema>:
- type ('string'): This is an intrinsic type of the property. For AiString, this is always 'string'.
- title (string): The title of the schema that describes the property.
- description (string): The description of the schema that describes the property.
- pattern (RegExp, optional): A regular expression pattern that the string should match. This field is converted from RegExp to string if needed.
- minLength (number, optional): The minimum length of the string. It must be a non-negative number.
- maxLength (number, optional): The maximum length of the string. It must be a positive number which must be greater than minLength.
Boolean
export const AiBoolean =
(description: string, options?: PropertyOptions<BooleanSchema>): PropertyDecorator =>
(target: object, propertyKey: string | symbol) => {
...
};
The AiBoolean function is used for declaring a boolean type property and its associated attributes such as description. It returns a PropertyDecorator function that is used to decorate (or add metadata to) a property.
Parameters
- description (string): Description for the property. This field is used to describe the functionality or usage of the property that is being decorated.
- options (PropertyOptions , optional): Options for setting extra parameters for your property. If not specified, default values will be used.
Example
class MyClass {
@AiBoolean('Is the user active?')
isActive: boolean;
}
Options
PropertyOptions<BooleanSchema> is a TypeScript type that defines an object structure. The fields for PropertyOptions<BooleanSchema> are:
- type ('boolean'): This is the type of the property. For AiBoolean, this always returns 'boolean'.
- title (string): The title of the schema that describes the property.
- description (string): A description of the schema that describes the property.
Enum
export const AiEnum =
(
description: string,
enum_type_or_values: Enum<unknown> | unknown[],
options?: PropertyOptions<StringSchema>,
): PropertyDecorator =>
(target: object, propertyKey: string | symbol) => {
...
};
The AiEnum function is used to define an enumeration type property. It describes a set of named constants mapped to a specific value (string or integer). It returns a PropertyDecorator function that can be used to add metadata to a property.
Parameters
- description (string): Description of the property. This field is used to describe the purpose or function of the property it decorates.
- enum_type_or_values (Enum | Unknown[]): Either an enum object or a simple array of values. This parameter identifies the particular set of allowed values for the property.
- options (PropertyOptions , optional): Options for setting extra parameters for your property. If not specified, default values will be used.
Example
enum Color {
Red = 'RED',
Green = 'GREEN',
Blue = 'BLUE'
}
class MyClass {
@AiEnum('Color of the object', Color)
objectColor: Color;
}
Options
PropertyOptions<StringSchema> is a TypeScript type representing an object structure. The fields of PropertyOptions<StringSchema> are:
- type ('string'): The type of property. In the case of AiEnum, this can be either "string" or a number according to the enum's values.
- title (string): Title of the schema describing the property.
- description (string): A description of the schema describing the property.
- enum (Array): This is an array field to hold the allowed values or referenced enum for the property.
Null
export const AiNull =
(description: string, options?: PropertyOptions<NullSchema>): PropertyDecorator =>
(target: object, propertyKey: string | symbol) => {
...
};
The AiNull function is used to define a Null type property in a target object. It returns a PropertyDecorator function, which is used to decorate and add metadata to a property.
Parameters
- description (string): Description of the property. This field is used to describe the purpose or function of the property it decorates.
- options (PropertyOptions , optional): An optional PropertyOptions type parameter for setting extra parameters for your property. If not specified, the default options will apply.
Example
class MyClass {
@AiNull('This property is expected to be null')
property: null;
}
Options
PropertyOptions<NullSchema> is a TypeScript type representing an object structure. The fields of PropertyOptions<NullSchema> are:
- type ('null'): The intrinsic type of the property. For AiNull, this always returns 'null'.
- title (string): Title of the schema describing the property.
- description (string): A description of the schema describing the property.
Number
export const AiNumber =
(
description: string,
options?: PropertyOptions<NumberSchema> & { type?: 'number' | 'integer' },
): PropertyDecorator =>
(target: object, propertyKey: string | symbol) => {
...
};
The AiNumber function is used for declaring a number type property along with its related attributes such as description. It returns a PropertyDecorator function that is used to decorate a property.
Parameters
- description (string): Description for the property. This field is used to describe the purpose or use of the property that it decorates.
- options (PropertyOptions & { type?: 'number' | 'integer' }, optional): Options for setting extra parameters for your property. This parameter could also define the type to be either 'number' or 'integer'. If not specified, the default type is 'number'.
Example
class MyClass {
@AiNumber('This is my numeric property', { type: 'integer' })
property: number;
}
Options
PropertyOptions<NumberSchema> is a TypeScript type that represents an object structure. The fields of PropertyOptions<NumberSchema> are:
- type ('number'|'integer'): This is the type of the property. In the case of AiNumber, the type can be either "number" or "integer".
- title (string): The title of the schema that describes the property.
- description (string): The description of the schema that describes the property.
Object
export const AiObject =
(description: string, options?: PropertyOptions<ObjectSchema>): PropertyDecorator =>
(target: object, propertyKey: string | symbol) => {
...
};
The AiObject function is used for defining an object type property in a target object. It includes a description and any relevant options for the property. It then returns a PropertyDecorator function which is used to decorate the property and associate it with the provided metadata.
Parameters
- description (string): Description for the property. This field is used to describe the purpose or function of the property it decorates.
- options (PropertyOptions , optional): Additional options for specifying custom properties for the object. If not specified, default options will apply.
Example
class InnerClass {
@AiString('Inner property')
innerProperty: string;
}
class MyClass {
@AiObject('This is my object property')
property: InnerClass;
}
Options
PropertyOptions<ObjectSchema> is a TypeScript type that represents an object structure. It contains:
- type ('object'): This is the intrinsic type of the property. For AiObject, this always returns 'object'.
- properties (Record<string, any>): This field is an object that maps property names as keys to their corresponding schema definitions as values. The schema definitions are themselves objects containing the respective type and any other associated information.
- title (string): The title of the schema that describes the property.
- description (string): The description of the schema that describes the property. Note: The required field will automatically be populated based on whether individual properties are marked as optional or not.
Optional
export const AiOptional: PropertyDecorator = (target: Object, propertyKey: string | symbol) => {
...
};
The AiOptional function is a PropertyDecorator that is used to mark a property as optional within an Object.
Parameters
- target (Object): The object on which to define the metadata.
- propertyKey (string | symbol): The property key for which to define metadata.
Example
class MyClass {
@AiOptional
@AiString('This is an optional string property')
optionalProperty?: string;
}
In this example, optionalProperty is marked as optional. Thus, it is not mandatory to provide a value for this property when creating an instance of MyClass.
Function Call
export const FunctionCall =
(options: OpenAiFunctionOptions): ClassDecorator =>
(target: Function) => {
...
};
The FunctionCall function is a ClassDecorator that can be used to associate metadata with the function (target). This metadata is typically used for customizing aspects of the function's behavior or appearance.
Parameters
- options (OpenAiFunctionOptions): Options that can be used to customize the metadata for the function.
Example
@FunctionCall({
name: 'MyFunction',
description: 'This is my custom function' })
class MyClass {
// your code here
}
In this example, the FunctionCall decorator is applied to MyClass, providing additional metadata to the class based on the provided OpenAiFunctionOptions.
Options
OpenAiFunctionOptions is an interface with the following properties:
- name (string, optional): The name of the function. It isn't mandatory but recommended for clarity and readability.
- description (string): The description of the function, describing what the function does. This is a required property.
Large Example
class TestingStrings {
@AiString('A testing string', { pattern: /^(\\([0-9]{3}\\))?[0-9]{3}-[0-9]{4}$/ })
test_regex!: string;
@AiString('A testing string', {
minLength: 3,
maxLength: 6,
examples: ['qwer', 'asdf', 'zxcv'],
default: '12as',
})
test_length!: string;
@AiString('A testing string', { format: EStringFormat.DATE_TIME })
test_datetime!: string;
@AiString('A testing string', { format: EStringFormat.DATE })
test_date!: string;
@AiString('A testing string', { format: EStringFormat.TIME })
test_time!: string;
@AiString('A testing string', { format: EStringFormat.EMAIL })
test_email!: string;
@AiString('A testing string', { format: EStringFormat.HOSTNAME })
test_hostname!: string;
@AiString('A testing string. Point to a real absolute JSON pointer in this object', {
format: EStringFormat.JSON_POINTER,
})
test_json_pointer!: string;
@AiString('A testing string.', {
format: EStringFormat.UUID,
})
test_uuid!: string;
@AiString('What are the example strings I gave you in test_length?')
test_examples!: string;
}
class TestingNumbers {
@AiNumber('A testing number')
test_number!: number;
@AiNumber('lowest number', { minimum: 12, maximum: 25 })
testing_range!: number;
@AiNumber('A testing number', { minimum: 10, maximum: 100, multipleOf: 3 })
testing_range_multiple!: number;
@AiNumber('A testing number', { multipleOf: 5 })
testing_multiple!: number;
@AiNumber('A testing float')
testing_float!: number;
}
class TestingArrays {
@AiArray('testing strings', [String])
strings!: string[];
@AiArray('testing strings', [Boolean])
booleans!: string[];
@AiArray('testing strings', [Number])
numbers!: string[];
@AiArray('testing strings', [Object])
objects!: string[];
@AiBoolean('a testing true boolean')
true_boolean!: boolean;
@AiBoolean('a testing false boolean')
false_boolean!: boolean;
@AiBoolean('a testing random boolean')
random_boolean!: boolean;
}
enum TestingE {
YES = 'yessssiiirrr!!',
NO = 'nahhhhh!',
MAYBE = 'maybeeee?',
}
@FunctionCall({
name: 'testing_function_call',
description: 'A testing function call.',
})
export class TestingJSON {
@AiObject('Testing strings class')
strings!: TestingStrings;
@AiObject('Testing number class')
numbers!: TestingNumbers;
@AiObject('Testing object class')
arrays!: TestingArrays;
@AiNull('a null type?')
['a null variable?']!: null;
@AiEnum('some enum', TestingE)
testing_enum!: TestingE;
@AiEnum('some enum', Object.values(TestingE))
testing_enum_2!: TestingE;
}
The latest updates on your projects. Learn more about Vercel for Git ↗︎
Name | Status | Preview | Comments | Updated (UTC) |
---|---|---|---|---|
langchainjs-api-refs | ✅ Ready (Inspect) | Visit Preview | 💬 Add feedback | Mar 9, 2024 0:05am |
langchainjs-docs | ✅ Ready (Inspect) | Visit Preview | Mar 9, 2024 0:05am |
This is really interesting! How does the actual conversion to JSON schema work?
I'm most worried about this affecting portability - how well supported are decorators in other environments/how do they affect bundlers? Or does TypeScript compilation just take care of it?
Could you write a test or two showing an end to end usecase?
Can you also run yarn format
and yarn lint
please?
TypeScript compiler takes care of emitting the decorators. For requirements, the user would need to make sure they import reflect-metadata
Ass well as enable these tsconfig fields:
{
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
...
As far as TypeScript compatibility, I believe TS ^5.x is needed, but I would need to test further.
I'll write tests, and linting, and formatting i've ensured it was done, but I'll make sure. My IDE handles it
I've used decorators with bundlers, and it works fine. It's all on compile time.
This is really interesting! How does the actual conversion to JSON schema work?
I'm most worried about this affecting portability - how well supported are decorators in other environments/how do they affect bundlers? Or does TypeScript compilation just take care of it?
Could you write a test or two showing an end to end usecase?
Can you also run
yarn format
andyarn lint
please?
For compatibility issues, and legacy reasons, a lot modern frameworks I've used for backend rely on decorators, so I dont see it as an obsolete pattern.
How does the actual conversion to JSON schema work? Pretty much, depending on which decorator you use it'll grab the options you provided, and store the partial JSON schema to the property metadata, then when its time to assemble everything, the convert function consolidates everything.
The reason why the class comes in handy, is when you use the JsonFunctionOutputParser, you can pass the class as a Generic, and the output of the stream/invoke will still be typed correctly.
The reason why the class comes in handy, is when you use the JsonFunctionOutputParser, you can pass the class as a Generic, and the output of the stream/invoke will still be typed correctly.
I think I get it, but a test case with that parser would be a good thing to add.
Also, you've written up some great docs in the comment above, but we'd want users to be able to discover it from the docs under https://js.langchain.com - would you mind porting it over there into a .mdx
file?
I think I would put it under https://js.langchain.com/docs/guides as Experimental -> Structured Output Decorators
or something similar along with exact setup instructions for how an end user should configure their tsconfig.json
. It's great that it's in and works, but ultimately if nobody can discover it, it's not going to help anyone!
Yes, I'll try to get a docs cooked up, make it more concise, since there is alot of information, that can most likely be condensed, as well as most of the properties are documentation in its name
, also I will push those test once I get it up and going. Trying to juggle this and work :)
I think I get it, but a test case with that parser would be a good thing to add.
For the parser, its not a parser, I'm using the original JsonFunctionOutputParser
, and just adding the generic type to it. Example, new JsonFunctionOutputParser<DecoratedClass>();
So its just a nice implementation to something that already exists. Since their input schema will match the type definitions for the output parser.
Edit: I just reread and realized what you said. It's hard to test since it's a Generic type, and Types are lost in runtime.
Still in progress. Been very busy plus was on vacation recently and trying to catch up with my projects.
The reason why the class comes in handy, is when you use the JsonFunctionOutputParser, you can pass the class as a Generic, and the output of the stream/invoke will still be typed correctly.
I think I get it, but a test case with that parser would be a good thing to add.
Also, you've written up some great docs in the comment above, but we'd want users to be able to discover it from the docs under https://js.langchain.com - would you mind porting it over there into a
.mdx
file?I think I would put it under https://js.langchain.com/docs/guides as
Experimental -> Structured Output Decorators
or something similar along with exact setup instructions for how an end user should configure theirtsconfig.json
. It's great that it's in and works, but ultimately if nobody can discover it, it's not going to help anyone!
I was thinking that under the 'OpenAIJsonFunctionOutputParser' would be a good place to put it. Since it's designed to help with the usage of this existing feature.
@jacoblee93 Should the code be in langchain
or in langchain-core
I'm a little confused by it.