-
Notifications
You must be signed in to change notification settings - Fork 0
/
method.ts
121 lines (114 loc) · 3.69 KB
/
method.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
import { Schema } from './schema';
/**
* This is the definition of a method that can be called by the LLM.
* The input and output are defined by a {@link Schema}.
* The handler is an async function that takes the input and returns the output.
* Methods can be added to a {@link Service}. The services can be added to a {@link Chat}.
*
* @template Input The input type for this method. This will be generated by the LLM.
* @template Output The output type for this method. This will be interpreted by the LLM.
*
* @example
* Here's a simple example:
* ```ts
* const isEvenMethod = new Method<number, boolean>(
* async (input) => input % 2 === 0,
* { type: 'number' },
* { type: 'boolean' }
* );
* const result = await isEvenMethod.handler(123); // false
*
* ```
* @example
* Here's a more complex example:
* ```ts
* type ReserveIn = { hotelId: number; roomId: number; userEmail: string };
* type ReserveOut = { reservationId?: number; error?: string };
* const reserveHotelRoomMethod = new Method<ReserveIn, ReserveOut>(
* {
* async (input) => {
* const { data } = await axios.post('/user', input);
* if (data.error) {
* return { error: data.error };
* }
* return { reservationId: data.reservationId };
* }
* type: 'object',
* properties: {
* hotelId: { type: 'number' },
* roomId: { type: 'number' },
* userEmail: { type: 'string' },
* },
* required: ['hotelId', 'roomId', 'userEmail'],
* },
* {
* type: 'object',
* properties: {
* reservationId: { type: 'number' },
* error: { type: 'string' },
* },
* },
* );
* ```
*/
export class Method<Input, Output> {
private _handler: (input: Input) => Promise<Output>;
private _input: Schema<Input>;
private _output?: Schema<Output>;
private _description?: string;
private _keywords?: string[];
get handler() {
return this._handler;
}
constructor(config: {
/**
* The async function that handles the method call. It takes an input of type `Input`
* and returns a value of type `Output` or a Promise that resolves to `Output`.
*/
handler: (input: Input) => Promise<Output>;
/**
* This {@link Schema} defines the input type. It can be a simple type like `number` or `string`,
* or it can be a complex type like an object or an array. Nested objects and arrays are also supported.
*
* The expectation is that this methods input will be generated by a LLM. The LLM will generate the input
* based on the input definition.
*/
input: Schema<Input>;
/**
* This {@link Schema} defines the output type. It can be a simple type like `number` or `string`,
* or it can be a complex type like an object or an array. Nested objects and arrays are also supported.
*/
output?: Schema<Output>;
/**
* This description can be used to describe the method. This can be read by the LLM.
*/
description?: string;
/**
* A number of keywords that describe the method. This can be used by the LLM to filter methods.
*/
keywords?: string[];
}) {
this._handler = config.handler;
this._input = config.input;
this._output = config.output;
this._description = config.description;
this._keywords = config.keywords;
}
describe() {
// Deep clone the object to prevent accidental mutation.
return JSON.parse(
JSON.stringify({
description: this._description,
keywords: this._keywords,
input: this._input,
output: this._output,
}),
);
}
get input(): Readonly<Schema<Input>> {
return this._input;
}
get output(): Readonly<Schema<Output> | undefined> {
return this._output;
}
}