langchain 0.0.108 → 0.0.109
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/dist/callbacks/handlers/tracer.d.ts +1 -1
- package/dist/callbacks/handlers/tracer_langchain.cjs +2 -2
- package/dist/callbacks/handlers/tracer_langchain.d.ts +2 -2
- package/dist/callbacks/handlers/tracer_langchain.js +1 -1
- package/dist/chat_models/openai.cjs +3 -0
- package/dist/chat_models/openai.js +3 -0
- package/dist/client/index.cjs +2 -2
- package/dist/client/index.d.ts +1 -1
- package/dist/client/index.js +1 -1
- package/dist/client/{langchainplus.cjs → langsmith.cjs} +3 -3
- package/dist/client/{langchainplus.d.ts → langsmith.d.ts} +1 -1
- package/dist/client/{langchainplus.js → langsmith.js} +2 -2
- package/dist/evaluation/run_evaluators/base.d.ts +2 -2
- package/dist/evaluation/run_evaluators/implementations.d.ts +2 -2
- package/dist/text_splitter.cjs +11 -4
- package/dist/text_splitter.d.ts +2 -0
- package/dist/text_splitter.js +11 -4
- package/dist/tools/dataforseo_api_search.cjs +279 -0
- package/dist/tools/dataforseo_api_search.d.ts +160 -0
- package/dist/tools/dataforseo_api_search.js +275 -0
- package/dist/tools/index.cjs +5 -1
- package/dist/tools/index.d.ts +2 -0
- package/dist/tools/index.js +2 -0
- package/dist/tools/searxng_search.cjs +129 -0
- package/dist/tools/searxng_search.d.ts +84 -0
- package/dist/tools/searxng_search.js +125 -0
- package/package.json +2 -2
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { KVMap, BaseRun } from "
|
|
1
|
+
import { KVMap, BaseRun } from "langsmith/schemas";
|
|
2
2
|
import { AgentAction, AgentFinish, BaseMessage, ChainValues, LLMResult } from "../../schema/index.js";
|
|
3
3
|
import { Serialized } from "../../load/serializable.js";
|
|
4
4
|
import { BaseCallbackHandler, BaseCallbackHandlerInput, NewTokenIndices } from "../base.js";
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.LangChainTracer = void 0;
|
|
4
|
-
const
|
|
4
|
+
const langsmith_1 = require("langsmith");
|
|
5
5
|
const env_js_1 = require("../../util/env.cjs");
|
|
6
6
|
const tracer_js_1 = require("./tracer.cjs");
|
|
7
7
|
class LangChainTracer extends tracer_js_1.BaseTracer {
|
|
@@ -37,7 +37,7 @@ class LangChainTracer extends tracer_js_1.BaseTracer {
|
|
|
37
37
|
(0, env_js_1.getEnvironmentVariable)("LANGCHAIN_PROJECT") ??
|
|
38
38
|
(0, env_js_1.getEnvironmentVariable)("LANGCHAIN_SESSION");
|
|
39
39
|
this.exampleId = exampleId;
|
|
40
|
-
this.client = client ?? new
|
|
40
|
+
this.client = client ?? new langsmith_1.Client({});
|
|
41
41
|
}
|
|
42
42
|
async _convertToCreate(run, example_id = undefined) {
|
|
43
43
|
return {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { Client } from "
|
|
2
|
-
import { BaseRun, RunUpdate as BaseRunUpdate } from "
|
|
1
|
+
import { Client } from "langsmith";
|
|
2
|
+
import { BaseRun, RunUpdate as BaseRunUpdate } from "langsmith/schemas";
|
|
3
3
|
import { BaseTracer } from "./tracer.js";
|
|
4
4
|
import { BaseCallbackHandlerInput } from "../base.js";
|
|
5
5
|
export interface Run extends BaseRun {
|
|
@@ -589,6 +589,9 @@ class PromptLayerChatOpenAI extends ChatOpenAI {
|
|
|
589
589
|
else if (message._getType() === "ai") {
|
|
590
590
|
messageDict = { role: "assistant", content: message.content };
|
|
591
591
|
}
|
|
592
|
+
else if (message._getType() === "function") {
|
|
593
|
+
messageDict = { role: "assistant", content: message.content };
|
|
594
|
+
}
|
|
592
595
|
else if (message._getType() === "system") {
|
|
593
596
|
messageDict = { role: "system", content: message.content };
|
|
594
597
|
}
|
|
@@ -582,6 +582,9 @@ export class PromptLayerChatOpenAI extends ChatOpenAI {
|
|
|
582
582
|
else if (message._getType() === "ai") {
|
|
583
583
|
messageDict = { role: "assistant", content: message.content };
|
|
584
584
|
}
|
|
585
|
+
else if (message._getType() === "function") {
|
|
586
|
+
messageDict = { role: "assistant", content: message.content };
|
|
587
|
+
}
|
|
585
588
|
else if (message._getType() === "system") {
|
|
586
589
|
messageDict = { role: "system", content: message.content };
|
|
587
590
|
}
|
package/dist/client/index.cjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.runOnDataset = void 0;
|
|
4
|
-
var
|
|
5
|
-
Object.defineProperty(exports, "runOnDataset", { enumerable: true, get: function () { return
|
|
4
|
+
var langsmith_js_1 = require("./langsmith.cjs");
|
|
5
|
+
Object.defineProperty(exports, "runOnDataset", { enumerable: true, get: function () { return langsmith_js_1.runOnDataset; } });
|
package/dist/client/index.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export { DatasetRunResults, runOnDataset } from "./
|
|
1
|
+
export { DatasetRunResults, runOnDataset } from "./langsmith.js";
|
package/dist/client/index.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export { runOnDataset } from "./
|
|
1
|
+
export { runOnDataset } from "./langsmith.js";
|
|
@@ -5,7 +5,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
6
|
exports.runOnDataset = exports.isChain = exports.isChatModel = exports.isLLM = void 0;
|
|
7
7
|
const p_queue_1 = __importDefault(require("p-queue"));
|
|
8
|
-
const
|
|
8
|
+
const langsmith_1 = require("langsmith");
|
|
9
9
|
const tracer_langchain_js_1 = require("../callbacks/handlers/tracer_langchain.cjs");
|
|
10
10
|
const utils_js_1 = require("../stores/message/utils.cjs");
|
|
11
11
|
const stringifyError = (err) => {
|
|
@@ -86,7 +86,7 @@ const runChatModel = async (example, tracer, chatModel) => {
|
|
|
86
86
|
const runOnDataset = async (datasetName, llmOrChainFactory, { maxConcurrency = 8, numRepetitions = 1, projectName, client, } = {}) => {
|
|
87
87
|
const PQueue = "default" in p_queue_1.default ? p_queue_1.default.default : p_queue_1.default;
|
|
88
88
|
const queue = new PQueue({ concurrency: maxConcurrency });
|
|
89
|
-
const client_ = client ?? new
|
|
89
|
+
const client_ = client ?? new langsmith_1.Client({});
|
|
90
90
|
const examples = await client_.listExamples({ datasetName });
|
|
91
91
|
let projectName_;
|
|
92
92
|
if (projectName === undefined) {
|
|
@@ -98,7 +98,7 @@ const runOnDataset = async (datasetName, llmOrChainFactory, { maxConcurrency = 8
|
|
|
98
98
|
else {
|
|
99
99
|
projectName_ = projectName;
|
|
100
100
|
}
|
|
101
|
-
await client_.createProject({ projectName: projectName_
|
|
101
|
+
await client_.createProject({ projectName: projectName_ });
|
|
102
102
|
const results = examples.reduce((acc, example) => ({ ...acc, [example.id]: [] }), {});
|
|
103
103
|
const modelOrFactoryType = await getModelOrFactoryType(llmOrChainFactory);
|
|
104
104
|
await Promise.all(Array.from({ length: numRepetitions })
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import PQueueMod from "p-queue";
|
|
2
|
-
import { Client } from "
|
|
2
|
+
import { Client } from "langsmith";
|
|
3
3
|
import { LangChainTracer } from "../callbacks/handlers/tracer_langchain.js";
|
|
4
4
|
import { mapStoredMessagesToChatMessages } from "../stores/message/utils.js";
|
|
5
5
|
const stringifyError = (err) => {
|
|
@@ -89,7 +89,7 @@ export const runOnDataset = async (datasetName, llmOrChainFactory, { maxConcurre
|
|
|
89
89
|
else {
|
|
90
90
|
projectName_ = projectName;
|
|
91
91
|
}
|
|
92
|
-
await client_.createProject({ projectName: projectName_
|
|
92
|
+
await client_.createProject({ projectName: projectName_ });
|
|
93
93
|
const results = examples.reduce((acc, example) => ({ ...acc, [example.id]: [] }), {});
|
|
94
94
|
const modelOrFactoryType = await getModelOrFactoryType(llmOrChainFactory);
|
|
95
95
|
await Promise.all(Array.from({ length: numRepetitions })
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { Example, Run } from "
|
|
2
|
-
import { EvaluationResult, RunEvaluator } from "
|
|
1
|
+
import { Example, Run } from "langsmith";
|
|
2
|
+
import { EvaluationResult, RunEvaluator } from "langsmith/evaluation";
|
|
3
3
|
import { BaseOutputParser } from "../../schema/output_parser.js";
|
|
4
4
|
import { LLMChain } from "../../chains/llm_chain.js";
|
|
5
5
|
import { BaseChain } from "../../chains/base.js";
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { Example, Run } from "
|
|
2
|
-
import { EvaluationResult } from "
|
|
1
|
+
import { Example, Run } from "langsmith";
|
|
2
|
+
import { EvaluationResult } from "langsmith/evaluation";
|
|
3
3
|
import { RunEvaluatorInputMapper, RunEvaluatorChain, RunEvaluatorOutputParser } from "./base.js";
|
|
4
4
|
import { PromptTemplate } from "../../prompts/prompt.js";
|
|
5
5
|
import { BaseLanguageModel } from "../../base_language/index.js";
|
package/dist/text_splitter.cjs
CHANGED
|
@@ -31,9 +31,16 @@ class TextSplitter extends document_js_2.BaseDocumentTransformer {
|
|
|
31
31
|
writable: true,
|
|
32
32
|
value: false
|
|
33
33
|
});
|
|
34
|
+
Object.defineProperty(this, "lengthFunction", {
|
|
35
|
+
enumerable: true,
|
|
36
|
+
configurable: true,
|
|
37
|
+
writable: true,
|
|
38
|
+
value: (text) => text.length
|
|
39
|
+
});
|
|
34
40
|
this.chunkSize = fields?.chunkSize ?? this.chunkSize;
|
|
35
41
|
this.chunkOverlap = fields?.chunkOverlap ?? this.chunkOverlap;
|
|
36
42
|
this.keepSeparator = fields?.keepSeparator ?? this.keepSeparator;
|
|
43
|
+
this.lengthFunction = fields?.lengthFunction ?? ((text) => text.length);
|
|
37
44
|
if (this.chunkOverlap >= this.chunkSize) {
|
|
38
45
|
throw new Error("Cannot have chunkOverlap >= chunkSize");
|
|
39
46
|
}
|
|
@@ -74,7 +81,7 @@ class TextSplitter extends document_js_2.BaseDocumentTransformer {
|
|
|
74
81
|
let numberOfIntermediateNewLines = 0;
|
|
75
82
|
if (prevChunk) {
|
|
76
83
|
const indexChunk = text.indexOf(chunk);
|
|
77
|
-
const indexEndPrevChunk = text.indexOf(prevChunk) + prevChunk
|
|
84
|
+
const indexEndPrevChunk = text.indexOf(prevChunk) + this.lengthFunction(prevChunk);
|
|
78
85
|
const removedNewlinesFromSplittingText = text.slice(indexEndPrevChunk, indexChunk);
|
|
79
86
|
numberOfIntermediateNewLines = (removedNewlinesFromSplittingText.match(/\n/g) || []).length;
|
|
80
87
|
if (appendChunkOverlapHeader) {
|
|
@@ -120,7 +127,7 @@ class TextSplitter extends document_js_2.BaseDocumentTransformer {
|
|
|
120
127
|
const currentDoc = [];
|
|
121
128
|
let total = 0;
|
|
122
129
|
for (const d of splits) {
|
|
123
|
-
const _len = d
|
|
130
|
+
const _len = this.lengthFunction(d);
|
|
124
131
|
if (total + _len + (currentDoc.length > 0 ? separator.length : 0) >
|
|
125
132
|
this.chunkSize) {
|
|
126
133
|
if (total > this.chunkSize) {
|
|
@@ -137,7 +144,7 @@ which is longer than the specified ${this.chunkSize}`);
|
|
|
137
144
|
// - or if we still have any chunks and the length is long
|
|
138
145
|
while (total > this.chunkOverlap ||
|
|
139
146
|
(total + _len > this.chunkSize && total > 0)) {
|
|
140
|
-
total -= currentDoc[0]
|
|
147
|
+
total -= this.lengthFunction(currentDoc[0]);
|
|
141
148
|
currentDoc.shift();
|
|
142
149
|
}
|
|
143
150
|
}
|
|
@@ -224,7 +231,7 @@ class RecursiveCharacterTextSplitter extends TextSplitter {
|
|
|
224
231
|
let goodSplits = [];
|
|
225
232
|
const _separator = this.keepSeparator ? "" : separator;
|
|
226
233
|
for (const s of splits) {
|
|
227
|
-
if (s
|
|
234
|
+
if (this.lengthFunction(s) < this.chunkSize) {
|
|
228
235
|
goodSplits.push(s);
|
|
229
236
|
}
|
|
230
237
|
else {
|
package/dist/text_splitter.d.ts
CHANGED
|
@@ -5,6 +5,7 @@ export interface TextSplitterParams {
|
|
|
5
5
|
chunkSize: number;
|
|
6
6
|
chunkOverlap: number;
|
|
7
7
|
keepSeparator: boolean;
|
|
8
|
+
lengthFunction?: (text: string) => number;
|
|
8
9
|
}
|
|
9
10
|
export type TextSplitterChunkHeaderOptions = {
|
|
10
11
|
chunkHeader?: string;
|
|
@@ -16,6 +17,7 @@ export declare abstract class TextSplitter extends BaseDocumentTransformer imple
|
|
|
16
17
|
chunkSize: number;
|
|
17
18
|
chunkOverlap: number;
|
|
18
19
|
keepSeparator: boolean;
|
|
20
|
+
lengthFunction: (text: string) => number;
|
|
19
21
|
constructor(fields?: Partial<TextSplitterParams>);
|
|
20
22
|
transformDocuments(documents: Document[], chunkHeaderOptions?: TextSplitterChunkHeaderOptions): Promise<Document[]>;
|
|
21
23
|
abstract splitText(text: string): Promise<string[]>;
|
package/dist/text_splitter.js
CHANGED
|
@@ -28,9 +28,16 @@ export class TextSplitter extends BaseDocumentTransformer {
|
|
|
28
28
|
writable: true,
|
|
29
29
|
value: false
|
|
30
30
|
});
|
|
31
|
+
Object.defineProperty(this, "lengthFunction", {
|
|
32
|
+
enumerable: true,
|
|
33
|
+
configurable: true,
|
|
34
|
+
writable: true,
|
|
35
|
+
value: (text) => text.length
|
|
36
|
+
});
|
|
31
37
|
this.chunkSize = fields?.chunkSize ?? this.chunkSize;
|
|
32
38
|
this.chunkOverlap = fields?.chunkOverlap ?? this.chunkOverlap;
|
|
33
39
|
this.keepSeparator = fields?.keepSeparator ?? this.keepSeparator;
|
|
40
|
+
this.lengthFunction = fields?.lengthFunction ?? ((text) => text.length);
|
|
34
41
|
if (this.chunkOverlap >= this.chunkSize) {
|
|
35
42
|
throw new Error("Cannot have chunkOverlap >= chunkSize");
|
|
36
43
|
}
|
|
@@ -71,7 +78,7 @@ export class TextSplitter extends BaseDocumentTransformer {
|
|
|
71
78
|
let numberOfIntermediateNewLines = 0;
|
|
72
79
|
if (prevChunk) {
|
|
73
80
|
const indexChunk = text.indexOf(chunk);
|
|
74
|
-
const indexEndPrevChunk = text.indexOf(prevChunk) + prevChunk
|
|
81
|
+
const indexEndPrevChunk = text.indexOf(prevChunk) + this.lengthFunction(prevChunk);
|
|
75
82
|
const removedNewlinesFromSplittingText = text.slice(indexEndPrevChunk, indexChunk);
|
|
76
83
|
numberOfIntermediateNewLines = (removedNewlinesFromSplittingText.match(/\n/g) || []).length;
|
|
77
84
|
if (appendChunkOverlapHeader) {
|
|
@@ -117,7 +124,7 @@ export class TextSplitter extends BaseDocumentTransformer {
|
|
|
117
124
|
const currentDoc = [];
|
|
118
125
|
let total = 0;
|
|
119
126
|
for (const d of splits) {
|
|
120
|
-
const _len = d
|
|
127
|
+
const _len = this.lengthFunction(d);
|
|
121
128
|
if (total + _len + (currentDoc.length > 0 ? separator.length : 0) >
|
|
122
129
|
this.chunkSize) {
|
|
123
130
|
if (total > this.chunkSize) {
|
|
@@ -134,7 +141,7 @@ which is longer than the specified ${this.chunkSize}`);
|
|
|
134
141
|
// - or if we still have any chunks and the length is long
|
|
135
142
|
while (total > this.chunkOverlap ||
|
|
136
143
|
(total + _len > this.chunkSize && total > 0)) {
|
|
137
|
-
total -= currentDoc[0]
|
|
144
|
+
total -= this.lengthFunction(currentDoc[0]);
|
|
138
145
|
currentDoc.shift();
|
|
139
146
|
}
|
|
140
147
|
}
|
|
@@ -219,7 +226,7 @@ export class RecursiveCharacterTextSplitter extends TextSplitter {
|
|
|
219
226
|
let goodSplits = [];
|
|
220
227
|
const _separator = this.keepSeparator ? "" : separator;
|
|
221
228
|
for (const s of splits) {
|
|
222
|
-
if (s
|
|
229
|
+
if (this.lengthFunction(s) < this.chunkSize) {
|
|
223
230
|
goodSplits.push(s);
|
|
224
231
|
}
|
|
225
232
|
else {
|
|
@@ -0,0 +1,279 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.DataForSeoAPISearch = void 0;
|
|
4
|
+
const env_js_1 = require("../util/env.cjs");
|
|
5
|
+
const base_js_1 = require("./base.cjs");
|
|
6
|
+
/**
|
|
7
|
+
* @class DataForSeoAPISearch
|
|
8
|
+
* @extends {Tool}
|
|
9
|
+
* @description Represents a wrapper class to work with DataForSEO SERP API.
|
|
10
|
+
*/
|
|
11
|
+
class DataForSeoAPISearch extends base_js_1.Tool {
|
|
12
|
+
/**
|
|
13
|
+
* @constructor
|
|
14
|
+
* @param {DataForSeoApiConfig} config
|
|
15
|
+
* @description Sets up the class, throws an error if the API login/password isn't provided.
|
|
16
|
+
*/
|
|
17
|
+
constructor(config = {}) {
|
|
18
|
+
super();
|
|
19
|
+
Object.defineProperty(this, "name", {
|
|
20
|
+
enumerable: true,
|
|
21
|
+
configurable: true,
|
|
22
|
+
writable: true,
|
|
23
|
+
value: "dataforseo-api-wrapper"
|
|
24
|
+
});
|
|
25
|
+
Object.defineProperty(this, "description", {
|
|
26
|
+
enumerable: true,
|
|
27
|
+
configurable: true,
|
|
28
|
+
writable: true,
|
|
29
|
+
value: "A robust Google Search API provided by DataForSeo. This tool is handy when you need information about trending topics or current events."
|
|
30
|
+
});
|
|
31
|
+
Object.defineProperty(this, "apiLogin", {
|
|
32
|
+
enumerable: true,
|
|
33
|
+
configurable: true,
|
|
34
|
+
writable: true,
|
|
35
|
+
value: void 0
|
|
36
|
+
});
|
|
37
|
+
Object.defineProperty(this, "apiPassword", {
|
|
38
|
+
enumerable: true,
|
|
39
|
+
configurable: true,
|
|
40
|
+
writable: true,
|
|
41
|
+
value: void 0
|
|
42
|
+
});
|
|
43
|
+
/**
|
|
44
|
+
* @property defaultParams
|
|
45
|
+
* @type {Record<string, string | number | boolean>}
|
|
46
|
+
* @description These are the default parameters to be used when making an API request.
|
|
47
|
+
*/
|
|
48
|
+
Object.defineProperty(this, "defaultParams", {
|
|
49
|
+
enumerable: true,
|
|
50
|
+
configurable: true,
|
|
51
|
+
writable: true,
|
|
52
|
+
value: {
|
|
53
|
+
location_name: "United States",
|
|
54
|
+
language_code: "en",
|
|
55
|
+
depth: 10,
|
|
56
|
+
se_name: "google",
|
|
57
|
+
se_type: "organic",
|
|
58
|
+
}
|
|
59
|
+
});
|
|
60
|
+
Object.defineProperty(this, "params", {
|
|
61
|
+
enumerable: true,
|
|
62
|
+
configurable: true,
|
|
63
|
+
writable: true,
|
|
64
|
+
value: {}
|
|
65
|
+
});
|
|
66
|
+
Object.defineProperty(this, "jsonResultTypes", {
|
|
67
|
+
enumerable: true,
|
|
68
|
+
configurable: true,
|
|
69
|
+
writable: true,
|
|
70
|
+
value: void 0
|
|
71
|
+
});
|
|
72
|
+
Object.defineProperty(this, "jsonResultFields", {
|
|
73
|
+
enumerable: true,
|
|
74
|
+
configurable: true,
|
|
75
|
+
writable: true,
|
|
76
|
+
value: void 0
|
|
77
|
+
});
|
|
78
|
+
Object.defineProperty(this, "topCount", {
|
|
79
|
+
enumerable: true,
|
|
80
|
+
configurable: true,
|
|
81
|
+
writable: true,
|
|
82
|
+
value: void 0
|
|
83
|
+
});
|
|
84
|
+
Object.defineProperty(this, "useJsonOutput", {
|
|
85
|
+
enumerable: true,
|
|
86
|
+
configurable: true,
|
|
87
|
+
writable: true,
|
|
88
|
+
value: false
|
|
89
|
+
});
|
|
90
|
+
const apiLogin = config.apiLogin ?? (0, env_js_1.getEnvironmentVariable)("DATAFORSEO_LOGIN");
|
|
91
|
+
const apiPassword = config.apiPassword ?? (0, env_js_1.getEnvironmentVariable)("DATAFORSEO_PASSWORD");
|
|
92
|
+
const params = config.params ?? {};
|
|
93
|
+
if (!apiLogin || !apiPassword) {
|
|
94
|
+
throw new Error("DataForSEO login or password not set. You can set it as DATAFORSEO_LOGIN and DATAFORSEO_PASSWORD in your .env file, or pass it to DataForSeoAPISearch.");
|
|
95
|
+
}
|
|
96
|
+
this.params = { ...this.defaultParams, ...params };
|
|
97
|
+
this.apiLogin = apiLogin;
|
|
98
|
+
this.apiPassword = apiPassword;
|
|
99
|
+
this.jsonResultTypes = config.jsonResultTypes;
|
|
100
|
+
this.jsonResultFields = config.jsonResultFields;
|
|
101
|
+
this.useJsonOutput = config.useJsonOutput ?? false;
|
|
102
|
+
this.topCount = config.topCount;
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* @method _call
|
|
106
|
+
* @param {string} keyword
|
|
107
|
+
* @returns {Promise<string>}
|
|
108
|
+
* @description Initiates a call to the API and processes the response.
|
|
109
|
+
*/
|
|
110
|
+
async _call(keyword) {
|
|
111
|
+
return this.useJsonOutput
|
|
112
|
+
? JSON.stringify(await this.results(keyword))
|
|
113
|
+
: this.processResponse(await this.getResponseJson(keyword));
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* @method results
|
|
117
|
+
* @param {string} keyword
|
|
118
|
+
* @returns {Promise<Array<any>>}
|
|
119
|
+
* @description Fetches the results from the API for the given keyword.
|
|
120
|
+
*/
|
|
121
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
122
|
+
async results(keyword) {
|
|
123
|
+
const res = await this.getResponseJson(keyword);
|
|
124
|
+
return this.filterResults(res, this.jsonResultTypes);
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* @method prepareRequest
|
|
128
|
+
* @param {string} keyword
|
|
129
|
+
* @returns {{url: string; headers: HeadersInit; data: BodyInit}}
|
|
130
|
+
* @description Prepares the request details for the API call.
|
|
131
|
+
*/
|
|
132
|
+
prepareRequest(keyword) {
|
|
133
|
+
if (this.apiLogin === undefined || this.apiPassword === undefined) {
|
|
134
|
+
throw new Error("api_login or api_password is not provided");
|
|
135
|
+
}
|
|
136
|
+
const credentials = Buffer.from(`${this.apiLogin}:${this.apiPassword}`, "utf-8").toString("base64");
|
|
137
|
+
const headers = {
|
|
138
|
+
Authorization: `Basic ${credentials}`,
|
|
139
|
+
"Content-Type": "application/json",
|
|
140
|
+
};
|
|
141
|
+
const params = { ...this.params };
|
|
142
|
+
params.keyword ??= keyword;
|
|
143
|
+
const data = [params];
|
|
144
|
+
return {
|
|
145
|
+
url: `https://api.dataforseo.com/v3/serp/${params.se_name}/${params.se_type}/live/advanced`,
|
|
146
|
+
headers,
|
|
147
|
+
data: JSON.stringify(data),
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
/**
|
|
151
|
+
* @method getResponseJson
|
|
152
|
+
* @param {string} keyword
|
|
153
|
+
* @returns {Promise<ApiResponse>}
|
|
154
|
+
* @description Executes a POST request to the provided URL and returns a parsed JSON response.
|
|
155
|
+
*/
|
|
156
|
+
async getResponseJson(keyword) {
|
|
157
|
+
const requestDetails = this.prepareRequest(keyword);
|
|
158
|
+
const response = await fetch(requestDetails.url, {
|
|
159
|
+
method: "POST",
|
|
160
|
+
headers: requestDetails.headers,
|
|
161
|
+
body: requestDetails.data,
|
|
162
|
+
});
|
|
163
|
+
if (!response.ok) {
|
|
164
|
+
throw new Error(`Got ${response.status} error from DataForSEO: ${response.statusText}`);
|
|
165
|
+
}
|
|
166
|
+
const result = await response.json();
|
|
167
|
+
return this.checkResponse(result);
|
|
168
|
+
}
|
|
169
|
+
/**
|
|
170
|
+
* @method checkResponse
|
|
171
|
+
* @param {ApiResponse} response
|
|
172
|
+
* @returns {ApiResponse}
|
|
173
|
+
* @description Checks the response status code.
|
|
174
|
+
*/
|
|
175
|
+
checkResponse(response) {
|
|
176
|
+
if (response.status_code !== 20000) {
|
|
177
|
+
throw new Error(`Got error from DataForSEO SERP API: ${response.status_message}`);
|
|
178
|
+
}
|
|
179
|
+
for (const task of response.tasks) {
|
|
180
|
+
if (task.status_code !== 20000) {
|
|
181
|
+
throw new Error(`Got error from DataForSEO SERP API: ${task.status_message}`);
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
return response;
|
|
185
|
+
}
|
|
186
|
+
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
187
|
+
/**
|
|
188
|
+
* @method filterResults
|
|
189
|
+
* @param {ApiResponse} res
|
|
190
|
+
* @param {Array<string> | undefined} types
|
|
191
|
+
* @returns {Array<any>}
|
|
192
|
+
* @description Filters the results based on the specified result types.
|
|
193
|
+
*/
|
|
194
|
+
filterResults(res, types) {
|
|
195
|
+
const output = [];
|
|
196
|
+
for (const task of res.tasks || []) {
|
|
197
|
+
for (const result of task.result || []) {
|
|
198
|
+
for (const item of result.items || []) {
|
|
199
|
+
if (types === undefined ||
|
|
200
|
+
types.length === 0 ||
|
|
201
|
+
types.includes(item.type)) {
|
|
202
|
+
const newItem = this.cleanupUnnecessaryItems(item);
|
|
203
|
+
if (Object.keys(newItem).length !== 0) {
|
|
204
|
+
output.push(newItem);
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
if (this.topCount !== undefined && output.length >= this.topCount) {
|
|
208
|
+
break;
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
return output;
|
|
214
|
+
}
|
|
215
|
+
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
216
|
+
/* eslint-disable no-param-reassign */
|
|
217
|
+
/**
|
|
218
|
+
* @method cleanupUnnecessaryItems
|
|
219
|
+
* @param {any} d
|
|
220
|
+
* @description Removes unnecessary items from the response.
|
|
221
|
+
*/
|
|
222
|
+
cleanupUnnecessaryItems(d) {
|
|
223
|
+
if (Array.isArray(d)) {
|
|
224
|
+
return d.map((item) => this.cleanupUnnecessaryItems(item));
|
|
225
|
+
}
|
|
226
|
+
const toRemove = ["xpath", "position", "rectangle"];
|
|
227
|
+
if (typeof d === "object" && d !== null) {
|
|
228
|
+
return Object.keys(d).reduce((newObj, key) => {
|
|
229
|
+
if ((this.jsonResultFields === undefined ||
|
|
230
|
+
this.jsonResultFields.includes(key)) &&
|
|
231
|
+
!toRemove.includes(key)) {
|
|
232
|
+
if (typeof d[key] === "object" && d[key] !== null) {
|
|
233
|
+
newObj[key] = this.cleanupUnnecessaryItems(d[key]);
|
|
234
|
+
}
|
|
235
|
+
else {
|
|
236
|
+
newObj[key] = d[key];
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
return newObj;
|
|
240
|
+
}, {});
|
|
241
|
+
}
|
|
242
|
+
return d;
|
|
243
|
+
}
|
|
244
|
+
/**
|
|
245
|
+
* @method processResponse
|
|
246
|
+
* @param {ApiResponse} res
|
|
247
|
+
* @returns {string}
|
|
248
|
+
* @description Processes the response to extract meaningful data.
|
|
249
|
+
*/
|
|
250
|
+
processResponse(res) {
|
|
251
|
+
let returnValue = "No good search result found";
|
|
252
|
+
for (const task of res.tasks || []) {
|
|
253
|
+
for (const result of task.result || []) {
|
|
254
|
+
const { item_types } = result;
|
|
255
|
+
const items = result.items || [];
|
|
256
|
+
if (item_types.includes("answer_box")) {
|
|
257
|
+
returnValue = items.find((item) => item.type === "answer_box").text;
|
|
258
|
+
}
|
|
259
|
+
else if (item_types.includes("knowledge_graph")) {
|
|
260
|
+
returnValue = items.find((item) => item.type === "knowledge_graph").description;
|
|
261
|
+
}
|
|
262
|
+
else if (item_types.includes("featured_snippet")) {
|
|
263
|
+
returnValue = items.find((item) => item.type === "featured_snippet").description;
|
|
264
|
+
}
|
|
265
|
+
else if (item_types.includes("shopping")) {
|
|
266
|
+
returnValue = items.find((item) => item.type === "shopping").price;
|
|
267
|
+
}
|
|
268
|
+
else if (item_types.includes("organic")) {
|
|
269
|
+
returnValue = items.find((item) => item.type === "organic").description;
|
|
270
|
+
}
|
|
271
|
+
if (returnValue) {
|
|
272
|
+
break;
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
return returnValue;
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
exports.DataForSeoAPISearch = DataForSeoAPISearch;
|