vectra 0.1.2 → 0.2.1

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.
Files changed (77) hide show
  1. package/README.md +5 -0
  2. package/bin/vectra.js +3 -0
  3. package/lib/GPT3Tokenizer.d.ts +9 -0
  4. package/lib/GPT3Tokenizer.d.ts.map +1 -0
  5. package/lib/GPT3Tokenizer.js +17 -0
  6. package/lib/GPT3Tokenizer.js.map +1 -0
  7. package/lib/ItemSelector.d.ts +1 -1
  8. package/lib/ItemSelector.d.ts.map +1 -1
  9. package/lib/ItemSelector.js.map +1 -1
  10. package/lib/LocalDocument.d.ts +16 -0
  11. package/lib/LocalDocument.d.ts.map +1 -0
  12. package/lib/LocalDocument.js +99 -0
  13. package/lib/LocalDocument.js.map +1 -0
  14. package/lib/LocalDocumentIndex.d.ts +48 -0
  15. package/lib/LocalDocumentIndex.d.ts.map +1 -0
  16. package/lib/LocalDocumentIndex.js +367 -0
  17. package/lib/LocalDocumentIndex.js.map +1 -0
  18. package/lib/LocalDocumentResult.d.ts +12 -0
  19. package/lib/LocalDocumentResult.d.ts.map +1 -0
  20. package/lib/LocalDocumentResult.js +186 -0
  21. package/lib/LocalDocumentResult.js.map +1 -0
  22. package/lib/LocalIndex.d.ts +9 -63
  23. package/lib/LocalIndex.d.ts.map +1 -1
  24. package/lib/LocalIndex.js +14 -1
  25. package/lib/LocalIndex.js.map +1 -1
  26. package/lib/OpenAIEmbeddings.d.ts +98 -0
  27. package/lib/OpenAIEmbeddings.d.ts.map +1 -0
  28. package/lib/OpenAIEmbeddings.js +139 -0
  29. package/lib/OpenAIEmbeddings.js.map +1 -0
  30. package/lib/TextSplitter.d.ts +17 -0
  31. package/lib/TextSplitter.d.ts.map +1 -0
  32. package/lib/TextSplitter.js +460 -0
  33. package/lib/TextSplitter.js.map +1 -0
  34. package/lib/WebFetcher.d.ts +16 -0
  35. package/lib/WebFetcher.d.ts.map +1 -0
  36. package/lib/WebFetcher.js +144 -0
  37. package/lib/WebFetcher.js.map +1 -0
  38. package/lib/index.d.ts +8 -0
  39. package/lib/index.d.ts.map +1 -1
  40. package/lib/index.js +13 -1
  41. package/lib/index.js.map +1 -1
  42. package/lib/internals/Colorize.d.ts +14 -0
  43. package/lib/internals/Colorize.d.ts.map +1 -0
  44. package/lib/internals/Colorize.js +64 -0
  45. package/lib/internals/Colorize.js.map +1 -0
  46. package/lib/internals/index.d.ts +3 -0
  47. package/lib/internals/index.d.ts.map +1 -0
  48. package/lib/internals/index.js +19 -0
  49. package/lib/internals/index.js.map +1 -0
  50. package/lib/internals/types.d.ts +42 -0
  51. package/lib/internals/types.d.ts.map +1 -0
  52. package/lib/internals/types.js +3 -0
  53. package/lib/internals/types.js.map +1 -0
  54. package/lib/types.d.ts +133 -0
  55. package/lib/types.d.ts.map +1 -0
  56. package/lib/types.js +3 -0
  57. package/lib/types.js.map +1 -0
  58. package/lib/vectra-cli.d.ts +2 -0
  59. package/lib/vectra-cli.d.ts.map +1 -0
  60. package/lib/vectra-cli.js +277 -0
  61. package/lib/vectra-cli.js.map +1 -0
  62. package/package.json +21 -3
  63. package/src/GPT3Tokenizer.ts +15 -0
  64. package/src/ItemSelector.ts +9 -9
  65. package/src/LocalDocument.ts +70 -0
  66. package/src/LocalDocumentIndex.ts +355 -0
  67. package/src/LocalDocumentResult.ts +206 -0
  68. package/src/LocalIndex.ts +12 -78
  69. package/src/OpenAIEmbeddings.ts +205 -0
  70. package/src/TextSplitter.ts +480 -0
  71. package/src/WebFetcher.ts +128 -0
  72. package/src/index.ts +8 -0
  73. package/src/internals/Colorize.ts +64 -0
  74. package/src/internals/index.ts +2 -0
  75. package/src/internals/types.ts +46 -0
  76. package/src/types.ts +160 -0
  77. package/src/vectra-cli.ts +238 -0
@@ -0,0 +1,206 @@
1
+ import { LocalDocument } from "./LocalDocument";
2
+ import { QueryResult, DocumentChunkMetadata, Tokenizer, DocumentTextSection } from "./types";
3
+
4
+ export class LocalDocumentResult extends LocalDocument {
5
+ private readonly _chunks: QueryResult<DocumentChunkMetadata>[];
6
+ private readonly _tokenizer: Tokenizer;
7
+ private readonly _score: number;
8
+
9
+ public constructor(folderPath: string, id: string, uri: string, chunks: QueryResult<DocumentChunkMetadata>[], tokenizer: Tokenizer) {
10
+ super(folderPath, id, uri);
11
+ this._chunks = chunks;
12
+ this._tokenizer = tokenizer;
13
+
14
+ // Compute average score
15
+ let score = 0;
16
+ this._chunks.forEach(chunk => score += chunk.score);
17
+ this._score = score / this._chunks.length;
18
+ }
19
+
20
+ public get chunks(): QueryResult<DocumentChunkMetadata>[] {
21
+ return this._chunks;
22
+ }
23
+
24
+ public get score(): number {
25
+ return this._score;
26
+ }
27
+
28
+ public async renderSections(maxTokens: number, maxSections: number): Promise<DocumentTextSection[]> {
29
+ // Load text from disk
30
+ const text = await this.loadText();
31
+
32
+ // First check to see if the entire document is less than maxTokens
33
+ const tokens = this._tokenizer.encode(text);
34
+ if (tokens.length < maxTokens) {
35
+ return [{
36
+ text,
37
+ tokenCount: tokens.length,
38
+ score: 1.0
39
+ }];
40
+ }
41
+
42
+ // Otherwise, we need to split the document into sections
43
+ // - Add each chunk to a temp array and filter out any chunk that's longer then maxTokens.
44
+ // - Sort the array by startPos to arrange chunks in document order.
45
+ // - Generate a new array of sections by combining chunks until the maxTokens is reached for each section.
46
+ // - Generate an aggregate score for each section by averaging the score of each chunk in the section.
47
+ // - Sort the sections by score and limit to maxSections.
48
+ // - For each remaining section combine adjacent chunks of text.
49
+ // - Dynamically add overlapping chunks of text to each section until the maxTokens is reached.
50
+ const chunks: SectionChunk[] = this._chunks.map(chunk => {
51
+ const startPos = chunk.item.metadata.startPos;
52
+ const endPos = chunk.item.metadata.endPos;
53
+ const chunkText = text.substring(startPos, endPos + 1);
54
+ return {
55
+ text: chunkText,
56
+ startPos,
57
+ endPos,
58
+ score: chunk.score,
59
+ tokenCount: this._tokenizer.encode(chunkText).length
60
+ };
61
+ }).filter(chunk => chunk.tokenCount <= maxTokens).sort((a, b) => a.startPos - b.startPos);
62
+
63
+ // Check for no chunks
64
+ if (chunks.length === 0) {
65
+ // Take the top chunk and return a subset of its text
66
+ const topChunk = this._chunks[0];
67
+ const startPos = topChunk.item.metadata.startPos;
68
+ const endPos = topChunk.item.metadata.endPos;
69
+ const chunkText = text.substring(startPos, endPos + 1);
70
+ const tokens = this._tokenizer.encode(chunkText);
71
+ return [{
72
+ text: this._tokenizer.decode(tokens.slice(0, maxTokens)),
73
+ tokenCount: maxTokens,
74
+ score: topChunk.score
75
+ }];
76
+ }
77
+
78
+ // Generate sections
79
+ const sections: Section[] = [{
80
+ chunks: [],
81
+ score: 0,
82
+ tokenCount: 0
83
+ }];
84
+ for (let i = 0; i < chunks.length; i++) {
85
+ const chunk = chunks[i];
86
+ let section = sections[sections.length - 1];
87
+ if (section.tokenCount + chunk.tokenCount > maxTokens) {
88
+ sections.push({
89
+ chunks: [],
90
+ score: 0,
91
+ tokenCount: 0
92
+ });
93
+ }
94
+ sections[sections.length - 1].chunks.push(chunk);
95
+ sections[sections.length - 1].score += chunk.score;
96
+ sections[sections.length - 1].tokenCount += chunk.tokenCount;
97
+ }
98
+
99
+ // Normalize section scores
100
+ sections.forEach(section => section.score /= section.chunks.length);
101
+
102
+ // Sort sections by score and limit to maxSections
103
+ sections.sort((a, b) => b.score - a.score);
104
+ if (sections.length > maxSections) {
105
+ sections.splice(maxSections, sections.length - maxSections);
106
+ }
107
+
108
+ // Combine adjacent chunks of text
109
+ sections.forEach(section => {
110
+ for (let i = 0; i < section.chunks.length - 1; i++) {
111
+ const chunk = section.chunks[i];
112
+ const nextChunk = section.chunks[i + 1];
113
+ if (chunk.endPos + 1 === nextChunk.startPos) {
114
+ chunk.text += nextChunk.text;
115
+ chunk.endPos = nextChunk.endPos;
116
+ chunk.tokenCount += nextChunk.tokenCount;
117
+ section.chunks.splice(i + 1, 1);
118
+ i--;
119
+ }
120
+ }
121
+ });
122
+
123
+ // Add overlapping chunks of text to each section until the maxTokens is reached
124
+ const connector: SectionChunk = {
125
+ text: '\n\n...\n\n',
126
+ startPos: -1,
127
+ endPos: -1,
128
+ score: 0,
129
+ tokenCount: this._tokenizer.encode('\n\n...\n\n').length
130
+ };
131
+ sections.forEach(section => {
132
+ // Insert connectors between chunks
133
+ if (section.chunks.length > 1) {
134
+ for (let i = 0; i < section.chunks.length - 1; i++) {
135
+ section.chunks.splice(i + 1, 0, connector);
136
+ section.tokenCount += connector.tokenCount;
137
+ i++;
138
+ }
139
+ }
140
+
141
+ // Add chunks to beginning and end of the section until maxTokens is reached
142
+ let budget = maxTokens - section.tokenCount;
143
+ if (budget > 40) {
144
+ const sectionStart = section.chunks[0].startPos;
145
+ const sectionEnd = section.chunks[section.chunks.length - 1].endPos;
146
+ if (sectionStart > 0) {
147
+ const beforeTex = text.substring(0, section.chunks[0].startPos);
148
+ const beforeTokens = this._tokenizer.encode(beforeTex);
149
+ const beforeBudget = sectionEnd < text.length - 1 ? Math.min(beforeTokens.length, Math.ceil(budget/2)) : Math.min(beforeTokens.length, budget);
150
+ const chunk: SectionChunk = {
151
+ text: this._tokenizer.decode(beforeTokens.slice(-beforeBudget)),
152
+ startPos: sectionStart - beforeBudget,
153
+ endPos: sectionStart - 1,
154
+ score: 0,
155
+ tokenCount: beforeBudget
156
+ };
157
+ section.chunks.unshift(chunk);
158
+ section.tokenCount += chunk.tokenCount;
159
+ budget -= chunk.tokenCount;
160
+ }
161
+
162
+ if (sectionEnd < text.length - 1) {
163
+ const afterText = text.substring(sectionEnd + 1);
164
+ const afterTokens = this._tokenizer.encode(afterText);
165
+ const afterBudget = Math.min(afterTokens.length, budget);
166
+ const chunk: SectionChunk = {
167
+ text: this._tokenizer.decode(afterTokens.slice(0, afterBudget)),
168
+ startPos: sectionEnd + 1,
169
+ endPos: sectionEnd + afterBudget,
170
+ score: 0,
171
+ tokenCount: afterBudget
172
+ };
173
+ section.chunks.push(chunk);
174
+ section.tokenCount += chunk.tokenCount;
175
+ budget -= chunk.tokenCount;
176
+ }
177
+ }
178
+ });
179
+
180
+ // Return final rendered sections
181
+ return sections.map(section => {
182
+ let text = '';
183
+ section.chunks.forEach(chunk => text += chunk.text);
184
+ return {
185
+ text: text,
186
+ tokenCount: section.tokenCount,
187
+ score: section.score
188
+ };
189
+ });
190
+ }
191
+ }
192
+
193
+ interface SectionChunk {
194
+ text: string;
195
+ startPos: number;
196
+ endPos: number;
197
+ score: number;
198
+ tokenCount: number;
199
+ }
200
+
201
+ interface Section {
202
+ chunks: SectionChunk[];
203
+ score: number;
204
+ tokenCount: number;
205
+ }
206
+
package/src/LocalIndex.ts CHANGED
@@ -2,6 +2,7 @@ import * as fs from 'fs/promises';
2
2
  import * as path from 'path';
3
3
  import { v4 } from 'uuid';
4
4
  import { ItemSelector } from './ItemSelector';
5
+ import { IndexItem, IndexStats, MetadataFilter, MetadataTypes, QueryResult } from './types';
5
6
 
6
7
  export interface CreateIndexConfig {
7
8
  version: number;
@@ -11,83 +12,6 @@ export interface CreateIndexConfig {
11
12
  };
12
13
  }
13
14
 
14
- export interface IndexStats {
15
- version: number;
16
- metadata_config: {
17
- indexed?: string[];
18
- };
19
- items: number;
20
- }
21
-
22
- export interface IndexItem<TMetadata = Record<string,MetadataTypes>> {
23
- id: string;
24
- metadata: TMetadata;
25
- vector: number[];
26
- norm: number;
27
- metadataFile?: string;
28
- }
29
-
30
- export interface QueryResult<TMetadata = Record<string,MetadataTypes>> {
31
- item: IndexItem<TMetadata>;
32
- score: number;
33
- }
34
-
35
- export interface MetadataFilter {
36
- [key: string]: MetadataTypes|MetadataFilter|(number|string)[]|MetadataFilter[];
37
-
38
- /**
39
- * Equal to (number, string, boolean)
40
- */
41
- '$eq': number|string|boolean;
42
-
43
- /**
44
- * Not equal to (number, string, boolean)
45
- */
46
- '$ne': number|string|boolean;
47
-
48
- /**
49
- * Greater than (number)
50
- */
51
- '$gt': number;
52
-
53
- /**
54
- * Greater than or equal to (number)
55
- */
56
- '$gte': number;
57
-
58
- /**
59
- * Less than (number)
60
- */
61
- '$lt': number;
62
-
63
- /**
64
- * Less than or equal to (number)
65
- */
66
- '$lte': number;
67
-
68
- /**
69
- * In array (string or number)
70
- */
71
- '$in': (number|string)[];
72
-
73
- /**
74
- * Not in array (string or number)
75
- */
76
- '$nin': (number|string)[];
77
-
78
- /**
79
- * AND (MetadataFilter[])
80
- */
81
- '$and': MetadataFilter[];
82
-
83
- /**
84
- * OR (MetadataFilter[])
85
- */
86
- '$or': MetadataFilter[];
87
- }
88
-
89
- export type MetadataTypes = number|string|boolean;
90
-
91
15
  /**
92
16
  * Local vector index instance.
93
17
  * @remarks
@@ -107,6 +31,13 @@ export class LocalIndex {
107
31
  this._folderPath = folderPath;
108
32
  }
109
33
 
34
+ /**
35
+ * Path to the index folder.
36
+ */
37
+ public get folderPath(): string {
38
+ return this._folderPath;
39
+ }
40
+
110
41
  /**
111
42
  * Begins an update to the index.
112
43
  * @remarks
@@ -364,7 +295,10 @@ export class LocalIndex {
364
295
  }
365
296
  }
366
297
 
367
- private async loadIndexData(): Promise<void> {
298
+ /**
299
+ * Ensures that the index has been loaded into memory.
300
+ */
301
+ protected async loadIndexData(): Promise<void> {
368
302
  if (this._data) {
369
303
  return;
370
304
  }
@@ -0,0 +1,205 @@
1
+ import axios, { AxiosInstance, AxiosResponse, AxiosRequestConfig } from 'axios';
2
+ import { EmbeddingsModel, EmbeddingsResponse } from "./types";
3
+ import { CreateEmbeddingRequest, CreateEmbeddingResponse, OpenAICreateEmbeddingRequest } from "./internals";
4
+
5
+ export interface BaseOpenAIEmbeddingsOptions {
6
+ /**
7
+ * Optional. Retry policy to use when calling the OpenAI API.
8
+ * @remarks
9
+ * The default retry policy is `[2000, 5000]` which means that the first retry will be after
10
+ * 2 seconds and the second retry will be after 5 seconds.
11
+ */
12
+ retryPolicy?: number[];
13
+
14
+ /**
15
+ * Optional. Request options to use when calling the OpenAI API.
16
+ */
17
+ requestConfig?: AxiosRequestConfig;
18
+ }
19
+
20
+ /**
21
+ * Options for configuring an `OpenAIEmbeddings` to generate embeddings using an OpenAI hosted model.
22
+ */
23
+ export interface OpenAIEmbeddingsOptions extends BaseOpenAIEmbeddingsOptions {
24
+ /**
25
+ * API key to use when calling the OpenAI API.
26
+ * @remarks
27
+ * A new API key can be created at https://platform.openai.com/account/api-keys.
28
+ */
29
+ apiKey: string;
30
+
31
+ /**
32
+ * Model to use for completion.
33
+ * @remarks
34
+ * For Azure OpenAI this is the name of the deployment to use.
35
+ */
36
+ model: string;
37
+
38
+ /**
39
+ * Optional. Organization to use when calling the OpenAI API.
40
+ */
41
+ organization?: string;
42
+
43
+ /**
44
+ * Optional. Endpoint to use when calling the OpenAI API.
45
+ * @remarks
46
+ * For Azure OpenAI this is the deployment endpoint.
47
+ */
48
+ endpoint?: string;
49
+ }
50
+
51
+ /**
52
+ * Options for configuring an `OpenAIEmbeddings` to generate embeddings using an Azure OpenAI hosted model.
53
+ */
54
+ export interface AzureOpenAIEmbeddingsOptions extends BaseOpenAIEmbeddingsOptions {
55
+ /**
56
+ * API key to use when making requests to Azure OpenAI.
57
+ */
58
+ azureApiKey: string;
59
+
60
+ /**
61
+ * Deployment endpoint to use.
62
+ */
63
+ azureEndpoint: string;
64
+
65
+ /**
66
+ * Name of the Azure OpenAI deployment (model) to use.
67
+ */
68
+ azureDeployment: string;
69
+
70
+ /**
71
+ * Optional. Version of the API being called. Defaults to `2023-05-15`.
72
+ */
73
+ azureApiVersion?: string;
74
+ }
75
+
76
+ /**
77
+ * A `PromptCompletionModel` for calling OpenAI and Azure OpenAI hosted models.
78
+ * @remarks
79
+ */
80
+ export class OpenAIEmbeddings implements EmbeddingsModel {
81
+ private readonly _httpClient: AxiosInstance;
82
+ private readonly _useAzure: boolean;
83
+
84
+ private readonly UserAgent = 'AlphaWave';
85
+
86
+ /**
87
+ * Options the client was configured with.
88
+ */
89
+ public readonly options: OpenAIEmbeddingsOptions|AzureOpenAIEmbeddingsOptions;
90
+
91
+ /**
92
+ * Creates a new `OpenAIClient` instance.
93
+ * @param options Options for configuring an `OpenAIClient`.
94
+ */
95
+ public constructor(options: OpenAIEmbeddingsOptions|AzureOpenAIEmbeddingsOptions) {
96
+ // Check for azure config
97
+ if ((options as AzureOpenAIEmbeddingsOptions).azureApiKey) {
98
+ this._useAzure = true;
99
+ this.options = Object.assign({
100
+ retryPolicy: [2000, 5000],
101
+ azureApiVersion: '2023-05-15',
102
+ }, options) as AzureOpenAIEmbeddingsOptions;
103
+
104
+ // Cleanup and validate endpoint
105
+ let endpoint = this.options.azureEndpoint.trim();
106
+ if (endpoint.endsWith('/')) {
107
+ endpoint = endpoint.substring(0, endpoint.length - 1);
108
+ }
109
+
110
+ if (!endpoint.toLowerCase().startsWith('https://')) {
111
+ throw new Error(`Client created with an invalid endpoint of '${endpoint}'. The endpoint must be a valid HTTPS url.`);
112
+ }
113
+
114
+ this.options.azureEndpoint = endpoint;
115
+ } else {
116
+ this._useAzure = false;
117
+ this.options = Object.assign({
118
+ retryPolicy: [2000, 5000]
119
+ }, options) as OpenAIEmbeddingsOptions;
120
+ }
121
+
122
+ // Create client
123
+ this._httpClient = axios.create({
124
+ validateStatus: (status) => status < 400 || status == 429
125
+ });
126
+ }
127
+
128
+ /**
129
+ * Creates embeddings for the given inputs using the OpenAI API.
130
+ * @param model Name of the model to use (or deployment for Azure).
131
+ * @param inputs Text inputs to create embeddings for.
132
+ * @returns A `EmbeddingsResponse` with a status and the generated embeddings or a message when an error occurs.
133
+ */
134
+ public async createEmbeddings(inputs: string | string[]): Promise<EmbeddingsResponse> {
135
+ const response = await this.createEmbeddingRequest({
136
+ input: inputs,
137
+ });
138
+
139
+ // Process response
140
+ if (response.status < 300) {
141
+ return { status: 'success', output: response.data.data.sort((a, b) => a.index - b.index).map((item) => item.embedding) };
142
+ } else if (response.status == 429) {
143
+ return { status: 'rate_limited', message: `The embeddings API returned a rate limit error.` }
144
+ } else {
145
+ return { status: 'error', message: `The embeddings API returned an error status of ${response.status}: ${response.statusText}` };
146
+ }
147
+ }
148
+
149
+ /**
150
+ * @private
151
+ */
152
+ protected createEmbeddingRequest(request: CreateEmbeddingRequest): Promise<AxiosResponse<CreateEmbeddingResponse>> {
153
+ if (this._useAzure) {
154
+ const options = this.options as AzureOpenAIEmbeddingsOptions;
155
+ const url = `${options.azureEndpoint}/openai/deployments/${options.azureDeployment}/embeddings?api-version=${options.azureApiVersion!}`;
156
+ return this.post(url, request);
157
+ } else {
158
+ const options = this.options as OpenAIEmbeddingsOptions;
159
+ const url = `${options.endpoint ?? 'https://api.openai.com'}/v1/embeddings`;
160
+ (request as OpenAICreateEmbeddingRequest).model = options.model;
161
+ return this.post(url, request);
162
+ }
163
+ }
164
+
165
+ /**
166
+ * @private
167
+ */
168
+ protected async post<TData>(url: string, body: object, retryCount = 0): Promise<AxiosResponse<TData>> {
169
+ // Initialize request config
170
+ const requestConfig: AxiosRequestConfig = Object.assign({}, this.options.requestConfig);
171
+
172
+ // Initialize request headers
173
+ if (!requestConfig.headers) {
174
+ requestConfig.headers = {};
175
+ }
176
+ if (!requestConfig.headers['Content-Type']) {
177
+ requestConfig.headers['Content-Type'] = 'application/json';
178
+ }
179
+ if (!requestConfig.headers['User-Agent']) {
180
+ requestConfig.headers['User-Agent'] = this.UserAgent;
181
+ }
182
+ if (this._useAzure) {
183
+ const options = this.options as AzureOpenAIEmbeddingsOptions;
184
+ requestConfig.headers['api-key'] = options.azureApiKey;
185
+ } else {
186
+ const options = this.options as OpenAIEmbeddingsOptions;
187
+ requestConfig.headers['Authorization'] = `Bearer ${options.apiKey}`;
188
+ if (options.organization) {
189
+ requestConfig.headers['OpenAI-Organization'] = options.organization;
190
+ }
191
+ }
192
+
193
+ // Send request
194
+ const response = await this._httpClient.post(url, body, requestConfig);
195
+
196
+ // Check for rate limit error
197
+ if (response.status == 429 && Array.isArray(this.options.retryPolicy) && retryCount < this.options.retryPolicy.length) {
198
+ const delay = this.options.retryPolicy[retryCount];
199
+ await new Promise((resolve) => setTimeout(resolve, delay));
200
+ return this.post(url, body, retryCount + 1);
201
+ } else {
202
+ return response;
203
+ }
204
+ }
205
+ }