@agentica/vector-selector 0.20.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/tools.ts ADDED
@@ -0,0 +1,99 @@
1
+ import type { AgenticaContext } from "@agentica/core";
2
+
3
+ export const Tools = {
4
+ extract_query: {
5
+ type: "function",
6
+ function: {
7
+ name: "extract_search_query",
8
+ description: "extract search query from user message\n",
9
+ parameters: {
10
+ type: "object",
11
+ properties: {
12
+ query_list: {
13
+ type: "array",
14
+ items: {
15
+ type: "object",
16
+ properties: {
17
+ reason: {
18
+ type: "string",
19
+ description: "The reason of the query selection.\n\nJust write the reason why you've determined to select this query.",
20
+ },
21
+ query: {
22
+ type: "string",
23
+ description: "the search query",
24
+ },
25
+ },
26
+ required: ["reason", "query"],
27
+ },
28
+ },
29
+ },
30
+ required: ["query_list"],
31
+ },
32
+ },
33
+ },
34
+
35
+ select_functions: {
36
+ type: "function",
37
+ function: {
38
+ name: "select_functions",
39
+ description: `Select proper API functions to call.
40
+
41
+ If you A.I. agent has found some proper API functions to call
42
+ from the conversation with user, please select the API functions
43
+ just by calling this function.
44
+
45
+ When user wants to call a same function multiply, you A.I. agent must
46
+ list up it multiply in the \`functions\` property. Otherwise the user has
47
+ requested to call many different functions, you A.I. agent have to assign
48
+ them all into the \`functions\` property.
49
+
50
+ Also, if you A.I. agent can't specify a specific function to call due to lack
51
+ of specificity or homogeneity of candidate functions, just assign all of them
52
+ by in the \`functions\` property too. Instead, when you A.I. agent can specify
53
+ a specific function to call, the others would be eliminated.
54
+
55
+ @example
56
+ \`\`\`json
57
+ [
58
+ {
59
+ "reason": "The user wants to call the function multiply.",
60
+ "function_name": "get_user_info"
61
+ },
62
+ {
63
+ "reason": "The user wants to modify the user info.",
64
+ "function_name": "modify_user_info"
65
+ }
66
+ ]
67
+ \`\`\`
68
+ `,
69
+ parameters: {
70
+ type: "object",
71
+ properties: {
72
+ function_list: {
73
+ type: "array",
74
+ items: {
75
+ type: "object",
76
+ properties: {
77
+ reason: {
78
+ type: "string",
79
+ description: "The reason of the function selection.\n\nJust write the reason why you've determined to select this function.",
80
+ },
81
+ function_name: {
82
+ type: "string",
83
+ description: "Name of the target function to call.",
84
+ },
85
+ },
86
+ required: ["reason", "function_name"],
87
+ },
88
+ },
89
+ },
90
+ required: ["function_list"],
91
+ },
92
+ },
93
+ },
94
+ } as const satisfies Record<
95
+ string,
96
+ NonNullable<
97
+ Parameters<AgenticaContext<"chatgpt">["request"]>[1]["tools"]
98
+ >[number]
99
+ >;
@@ -0,0 +1,185 @@
1
+ import { getRetry, groupByArray } from "./utils";
2
+
3
+ describe("getRetry", () => {
4
+ it("should throw error when count is less than 1", () => {
5
+ // @ts-expect-error getRetry should throw error when count is not more than 0
6
+ expect(() => getRetry(0)).toThrow("count should be greater than 0");
7
+ });
8
+
9
+ it("should retry the function specified number of times", async () => {
10
+ const mockFn = vi.fn().mockImplementation(async () => {
11
+ if (mockFn.mock.calls.length === 1) {
12
+ throw new Error("Failed");
13
+ }
14
+ return "success";
15
+ });
16
+
17
+ const retryFn = getRetry(3);
18
+ const result = await retryFn(mockFn);
19
+
20
+ expect(result).toBe("success");
21
+ expect(mockFn).toHaveBeenCalledTimes(2);
22
+ });
23
+
24
+ it("should throw the last error when all retries fail", async () => {
25
+ const error = new Error("Failed");
26
+ const mockFn = vi.fn().mockRejectedValue(error);
27
+
28
+ const retryFn = getRetry(3);
29
+ await expect(retryFn(mockFn)).rejects.toThrow(error);
30
+ expect(mockFn).toHaveBeenCalledTimes(3);
31
+ });
32
+
33
+ it("should handle noop functions", async () => {
34
+ const mockFn = vi.fn().mockResolvedValue(undefined);
35
+ const retryFn = getRetry(3);
36
+
37
+ const result = await retryFn(mockFn);
38
+ expect(result).toBeUndefined();
39
+ expect(mockFn).toHaveBeenCalledTimes(1);
40
+ });
41
+
42
+ it("should handle non-async functions", async () => {
43
+ const mockFn: () => string = vi.fn()
44
+ .mockImplementationOnce(() => { throw new Error("Failed"); })
45
+ .mockImplementationOnce(() => "success");
46
+
47
+ const retryFn = getRetry(3);
48
+ const result = await retryFn(async () => mockFn());
49
+
50
+ expect(result).toBe("success");
51
+ expect(mockFn).toHaveBeenCalledTimes(2);
52
+ });
53
+
54
+ it("should stop retrying after successful attempt", async () => {
55
+ const mockFn = vi.fn()
56
+ .mockRejectedValueOnce(new Error("Failed"))
57
+ .mockResolvedValueOnce("success");
58
+
59
+ const retryFn = getRetry(5);
60
+ const result = await retryFn(mockFn);
61
+
62
+ expect(result).toBe("success");
63
+ expect(mockFn).toHaveBeenCalledTimes(2);
64
+ });
65
+
66
+ it("should handle different types of errors", async () => {
67
+ const retryFn = getRetry(3);
68
+
69
+ await expect(retryFn(async () => {
70
+ throw new TypeError("Type error");
71
+ })).rejects.toThrow(TypeError);
72
+
73
+ await expect(retryFn(async () => {
74
+ throw new RangeError("Range error");
75
+ })).rejects.toThrow(RangeError);
76
+ });
77
+
78
+ it("should handle null and undefined return values", async () => {
79
+ const retryFn = getRetry(2);
80
+
81
+ const nullFn = async () => null;
82
+ const undefinedFn = async () => undefined;
83
+
84
+ expect(await retryFn(nullFn)).toBeNull();
85
+ expect(await retryFn(undefinedFn)).toBeUndefined();
86
+ });
87
+
88
+ it("should handle floating point calculations", async () => {
89
+ let attempts = 0;
90
+ const fn = async () => {
91
+ attempts++;
92
+ if (attempts < 2) {
93
+ throw new Error("Failed");
94
+ }
95
+ return 3.14159;
96
+ };
97
+
98
+ const retryFn = getRetry(3);
99
+ const result = await retryFn(fn);
100
+ expect(result).toBeCloseTo(3.14159);
101
+ expect(attempts).toBe(2);
102
+ });
103
+
104
+ it("should handle mathematical operations with floating points", async () => {
105
+ let attempts = 0;
106
+ const fn = async () => {
107
+ attempts++;
108
+ if (attempts < 2) {
109
+ throw new Error("Failed");
110
+ }
111
+ return 0.1 + 0.2; // Known floating point precision issue
112
+ };
113
+
114
+ const retryFn = getRetry(3);
115
+ const result = await retryFn(fn);
116
+ expect(result).toBeCloseTo(0.3);
117
+ expect(attempts).toBe(2);
118
+ });
119
+
120
+ it("should handle very small floating point numbers", async () => {
121
+ const retryFn = getRetry(2);
122
+ const fn = async () => 0.0000001;
123
+
124
+ const result = await retryFn(fn);
125
+ expect(result).toBeCloseTo(0.0000001);
126
+ });
127
+
128
+ it("should handle very large floating point numbers", async () => {
129
+ const retryFn = getRetry(2);
130
+ const fn = async () => 1e20;
131
+
132
+ const result = await retryFn(fn);
133
+ expect(result).toBe(1e20);
134
+ });
135
+ });
136
+
137
+ describe("groupByArray", () => {
138
+ it("should group array into chunks of specified size", () => {
139
+ const array = [1, 2, 3, 4, 5, 6];
140
+ const result = groupByArray(array, 2);
141
+ expect(result).toEqual([[1, 2], [3, 4], [5, 6]]);
142
+ });
143
+
144
+ it("should handle array with length not divisible by count", () => {
145
+ const array = [1, 2, 3, 4, 5];
146
+ const result = groupByArray(array, 2);
147
+ expect(result).toEqual([[1, 2], [3, 4], [5]]);
148
+ });
149
+
150
+ it("should return empty array when input array is empty", () => {
151
+ const result = groupByArray([], 2);
152
+ expect(result).toEqual([]);
153
+ });
154
+
155
+ it("should handle count larger than array length", () => {
156
+ const array = [1];
157
+ const result = groupByArray(array, 2);
158
+ expect(result).toEqual([[1]]);
159
+ });
160
+
161
+ it("should throw error when count is less than 1", () => {
162
+ const array = [1, 2, 3, 4, 5];
163
+ // @ts-expect-error groupByArray should throw error when count is not more than 0
164
+ expect(() => groupByArray(array, 0)).toThrow("count should be greater than 0");
165
+ });
166
+
167
+ it("should handle array with different data types", () => {
168
+ const mixedArray = [1, "two", { three: 3 }, [4], true, null, undefined];
169
+ const result = groupByArray(mixedArray, 2);
170
+ expect(result).toEqual([
171
+ [1, "two"],
172
+ [{ three: 3 }, [4]],
173
+ [true, null],
174
+ [undefined],
175
+ ]);
176
+ });
177
+
178
+ it("should handle very large arrays", () => {
179
+ const largeArray = Array.from({ length: 1000 }, (_, i) => i);
180
+ const result = groupByArray(largeArray, 100);
181
+ expect(result.length).toBe(10);
182
+ expect(result[0]?.length).toBe(100);
183
+ expect(result[9]?.length).toBe(100);
184
+ });
185
+ });
package/src/utils.ts ADDED
@@ -0,0 +1,105 @@
1
+ import type { GreaterThan, Integer } from "type-fest";
2
+
3
+ /** type function to check if a number is greater than 0 */
4
+ type GreaterThanZeroInteger<T extends number> = GreaterThan<Integer<T>, 0> extends true ? T : never;
5
+
6
+ /**
7
+ * This function is used to get a retry function.
8
+ *
9
+ * It will throw an error if the count is not a number,
10
+ * or if the count is not a finite number,
11
+ * or if the count is not an integer,
12
+ * or if the count is less than 1.
13
+ *
14
+ * @param count - The number of times to retry the function.
15
+ * @returns A retry function.
16
+ * @throws {TypeError} If the count is not a number, or if the count is not a finite number, or if the count is not an integer, or if the count is less than 1.
17
+ * @throws {Error} If the function fails to return a value after the specified number of retries.
18
+ */
19
+ export function getRetry<TCount extends number>(count: GreaterThanZeroInteger<TCount>) {
20
+ if (count < 1) {
21
+ throw new Error("count should be greater than 0");
22
+ }
23
+
24
+ return async <T>(fn: () => Promise<T>) => {
25
+ let lastError: unknown = null;
26
+
27
+ for (let i = 0; i < count; i++) {
28
+ try {
29
+ return await fn();
30
+ }
31
+ catch (e: unknown) {
32
+ lastError = e;
33
+ if (i === count - 1) {
34
+ throw e;
35
+ }
36
+ }
37
+ }
38
+ // count should be greater than 0.
39
+ throw lastError;
40
+ };
41
+ }
42
+
43
+ /**
44
+ * This function is used to group an array into a 2-dimensional array.
45
+ *
46
+ * It will throw an error if the count is not a number,
47
+ * or if the count is not a finite number,
48
+ * or if the count is not an integer,
49
+ * or if the count is less than 1.
50
+ *
51
+ * @param array - The array to group.
52
+ * @param count - The number of elements in each group.
53
+ * @returns A 2-dimensional array.
54
+ */
55
+ export function groupByArray<T, TCount extends number>(array: T[], count: GreaterThanZeroInteger<TCount>): T[][] {
56
+ if (count < 1) {
57
+ throw new Error("count should be greater than 0");
58
+ }
59
+
60
+ if (array.length === 0) {
61
+ return [];
62
+ }
63
+
64
+ if (array.length < count) {
65
+ return [array];
66
+ }
67
+
68
+ const grouped = [];
69
+ for (let i = 0; i < array.length; i += count) {
70
+ grouped.push(array.slice(i, i + count));
71
+ }
72
+ return grouped;
73
+ }
74
+
75
+ /**
76
+ * Removes duplicates from an array.
77
+ * You can specify which value to compare using a property selector function.
78
+ *
79
+ * @param array - Array to remove duplicates from
80
+ * @param selector - Function that selects the value to compare
81
+ * @returns New array with duplicates removed
82
+ *
83
+ * @example
84
+ * ```typescript
85
+ * const users = [
86
+ * { id: 1, name: 'John' },
87
+ * { id: 2, name: 'Jane' },
88
+ * { id: 1, name: 'John' }
89
+ * ];
90
+ *
91
+ * const uniqueUsers = uniqBy(users, user => user.id);
92
+ * // Result: [{ id: 1, name: 'John' }, { id: 2, name: 'Jane' }]
93
+ * ```
94
+ */
95
+ export function uniqBy<T, K>(array: T[], selector: (item: T) => K): T[] {
96
+ const seen = new Set<K>();
97
+ return array.filter((item) => {
98
+ const key = selector(item);
99
+ if (seen.has(key)) {
100
+ return false;
101
+ }
102
+ seen.add(key);
103
+ return true;
104
+ });
105
+ }