From 922453fefe6ccfcbcdc866380027fae705c2f0c3 Mon Sep 17 00:00:00 2001 From: Chase McDougall Date: Fri, 1 Dec 2023 12:51:07 -0500 Subject: [PATCH 01/44] remove run reference (#3481) --- CONTRIBUTING.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 71533c809bfe..bb3c21a172b6 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -230,7 +230,7 @@ example to showcase how to use it. Most of our users find examples to be the most helpful kind of documentation. Examples can be added in the `examples/src` directory, e.g. -`examples/src/path/to/example` and should export a `run` function. This +`examples/src/path/to/example`. This example can then be invoked with `yarn example path/to/example` at the top level of the repo. From f0ca6a31fc815044d609eac8068274aaad11d818 Mon Sep 17 00:00:00 2001 From: Brace Sproul Date: Fri, 1 Dec 2023 12:25:52 -0800 Subject: [PATCH 02/44] core[patch]: move more tests & test utils to core (#3483) * core[patch]: move more tests & test utils to core * cr * cr --- .../src/runnables}/tests/runnable.test.ts | 93 +++--- .../runnables/tests/runnable_binding.test.ts | 2 +- .../tests/runnable_stream_log.test.ts | 2 +- .../tests/runnable_with_fallbacks.test.ts | 2 +- langchain-core/src/utils/testing/index.ts | 291 +++++++++++++++++- langchain-core/src/utils/testing/lib.ts | 146 --------- 6 files changed, 343 insertions(+), 193 deletions(-) rename {langchain/src/schema => langchain-core/src/runnables}/tests/runnable.test.ts (85%) delete mode 100644 langchain-core/src/utils/testing/lib.ts diff --git a/langchain/src/schema/tests/runnable.test.ts b/langchain-core/src/runnables/tests/runnable.test.ts similarity index 85% rename from langchain/src/schema/tests/runnable.test.ts rename to langchain-core/src/runnables/tests/runnable.test.ts index ffd78a2c50c7..702e2a309193 100644 --- a/langchain/src/schema/tests/runnable.test.ts +++ b/langchain-core/src/runnables/tests/runnable.test.ts @@ -1,33 +1,28 @@ /* eslint-disable no-promise-executor-return */ /* eslint-disable @typescript-eslint/no-explicit-any */ -import { z } from "zod"; import { test } from "@jest/globals"; -import { createChatMessageChunkEncoderStream } from "../../chat_models/base.js"; import { ChatPromptTemplate, HumanMessagePromptTemplate, PromptTemplate, SystemMessagePromptTemplate, } from "../../prompts/index.js"; -import { StructuredOutputParser } from "../../output_parsers/structured.js"; -import { - RunnableMap, - RunnableSequence, - RouterRunnable, - RunnableLambda, -} from "../runnable/index.js"; -import { Document } from "../../document.js"; -import { OutputParserException, StringOutputParser } from "../output_parser.js"; - +import { Document } from "../../documents/document.js"; +import { createChatMessageChunkEncoderStream } from "../../language_models/chat_models.js"; +import { StringOutputParser } from "../../output_parsers/string.js"; import { FakeLLM, - FakeRetriever, FakeChatModel, - FakeRunnable, + FakeRetriever, FakeStreamingLLM, FakeSplitIntoListParser, -} from "./lib.js"; + FakeRunnable, +} from "../../utils/testing/index.js"; +import { RunnableSequence, RunnableMap, RunnableLambda } from "../base.js"; +import { RouterRunnable } from "../router.js"; +import { OutputParserException } from "../../output_parsers/base.js"; +import { BaseMessage } from "../../messages/index.js"; test("Test batch", async () => { const llm = new FakeLLM({}); @@ -73,34 +68,6 @@ test("Pipe from one runnable to the next", async () => { expect(result).toBe("Hello world!"); }); -test("Create a runnable sequence and run it", async () => { - const promptTemplate = PromptTemplate.fromTemplate("{input}"); - const llm = new FakeChatModel({}); - const parser = StructuredOutputParser.fromZodSchema( - z.object({ outputValue: z.string().describe("A test value") }) - ); - const text = `\`\`\` -{"outputValue": "testing"} -\`\`\``; - const runnable = promptTemplate.pipe(llm).pipe(parser); - const result = await runnable.invoke({ input: text }); - console.log(result); - expect(result).toEqual({ outputValue: "testing" }); -}); - -test("Create a runnable sequence with a static method with invalid output and catch the error", async () => { - const promptTemplate = PromptTemplate.fromTemplate("{input}"); - const llm = new FakeChatModel({}); - const parser = StructuredOutputParser.fromZodSchema( - z.object({ outputValue: z.string().describe("A test value") }) - ); - const runnable = RunnableSequence.from([promptTemplate, llm, parser]); - await expect(async () => { - const result = await runnable.invoke({ input: "Hello sequence!" }); - console.log(result); - }).rejects.toThrow(OutputParserException); -}); - test("Create a runnable sequence with a runnable map", async () => { const promptTemplate = ChatPromptTemplate.fromMessages<{ documents: string; @@ -247,3 +214,43 @@ test("Runnable withConfig", async () => { expect(chunks[0]?.tags).toEqual(["a-tag", "b-tag"]); expect(chunks[0]?.metadata).toEqual({ a: "updated", b: "c" }); }); + +test("Create a runnable sequence and run it", async () => { + const promptTemplate = PromptTemplate.fromTemplate("{input}"); + const llm = new FakeChatModel({}); + const parser = new StringOutputParser(); + const text = `Jello world`; + const runnable = promptTemplate.pipe(llm).pipe(parser); + const result = await runnable.invoke({ input: text }); + console.log(result); + expect(result).toEqual("Jello world"); +}); + +test("Create a runnable sequence with a static method with invalid output and catch the error", async () => { + const promptTemplate = PromptTemplate.fromTemplate("{input}"); + const llm = new FakeChatModel({}); + const parser = (input: BaseMessage) => { + console.log(input); + try { + const parsedInput = + typeof input.content === "string" + ? JSON.parse(input.content) + : input.content; + if ( + !("outputValue" in parsedInput) || + parsedInput.outputValue !== "Hello sequence!" + ) { + throw new Error("Test failed!"); + } else { + return input; + } + } catch (e) { + throw new OutputParserException("Invalid output"); + } + }; + const runnable = RunnableSequence.from([promptTemplate, llm, parser]); + await expect(async () => { + const result = await runnable.invoke({ input: "Hello sequence!" }); + console.log(result); + }).rejects.toThrow(OutputParserException); +}); diff --git a/langchain-core/src/runnables/tests/runnable_binding.test.ts b/langchain-core/src/runnables/tests/runnable_binding.test.ts index 891613492a40..e1b2ea74756b 100644 --- a/langchain-core/src/runnables/tests/runnable_binding.test.ts +++ b/langchain-core/src/runnables/tests/runnable_binding.test.ts @@ -2,7 +2,7 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ import { test } from "@jest/globals"; import { StringOutputParser } from "../../output_parsers/string.js"; -import { FakeChatModel, FakeStreamingLLM } from "../../utils/testing/lib.js"; +import { FakeChatModel, FakeStreamingLLM } from "../../utils/testing/index.js"; test("Bind kwargs to a runnable", async () => { const llm = new FakeChatModel({}); diff --git a/langchain-core/src/runnables/tests/runnable_stream_log.test.ts b/langchain-core/src/runnables/tests/runnable_stream_log.test.ts index d947901fbc65..8f7c1d1e589a 100644 --- a/langchain-core/src/runnables/tests/runnable_stream_log.test.ts +++ b/langchain-core/src/runnables/tests/runnable_stream_log.test.ts @@ -15,7 +15,7 @@ import { FakeLLM, FakeChatModel, FakeRetriever, -} from "../../utils/testing/lib.js"; +} from "../../utils/testing/index.js"; test("Runnable streamLog method", async () => { const promptTemplate = PromptTemplate.fromTemplate("{input}"); diff --git a/langchain-core/src/runnables/tests/runnable_with_fallbacks.test.ts b/langchain-core/src/runnables/tests/runnable_with_fallbacks.test.ts index dfc4e31ef068..650e36b1aad1 100644 --- a/langchain-core/src/runnables/tests/runnable_with_fallbacks.test.ts +++ b/langchain-core/src/runnables/tests/runnable_with_fallbacks.test.ts @@ -1,7 +1,7 @@ /* eslint-disable no-promise-executor-return */ /* eslint-disable @typescript-eslint/no-explicit-any */ import { test } from "@jest/globals"; -import { FakeLLM } from "../../utils/testing/lib.js"; +import { FakeLLM } from "../../utils/testing/index.js"; test("RunnableWithFallbacks", async () => { const llm = new FakeLLM({ diff --git a/langchain-core/src/utils/testing/index.ts b/langchain-core/src/utils/testing/index.ts index b7b4e0469ad2..f26fc33246bd 100644 --- a/langchain-core/src/utils/testing/index.ts +++ b/langchain-core/src/utils/testing/index.ts @@ -1 +1,290 @@ -export * from "./lib.js"; +/* eslint-disable no-promise-executor-return */ +/* eslint-disable @typescript-eslint/no-explicit-any */ + +import { + BaseCallbackConfig, + CallbackManagerForLLMRun, +} from "../../callbacks/manager.js"; +import { Document } from "../../documents/document.js"; +import { + BaseChatModel, + BaseChatModelParams, +} from "../../language_models/chat_models.js"; +import { LLM } from "../../language_models/llms.js"; +import { + BaseMessage, + AIMessage, + AIMessageChunk, +} from "../../messages/index.js"; +import { BaseOutputParser } from "../../output_parsers/base.js"; +import { + GenerationChunk, + type ChatResult, + ChatGenerationChunk, +} from "../../outputs.js"; +import { BaseRetriever } from "../../retrievers.js"; +import { Runnable } from "../../runnables/base.js"; + +/** + * Parser for comma-separated values. It splits the input text by commas + * and trims the resulting values. + */ +export class FakeSplitIntoListParser extends BaseOutputParser { + lc_namespace = ["tests", "fake"]; + + getFormatInstructions() { + return ""; + } + + async parse(text: string): Promise { + return text.split(",").map((value) => value.trim()); + } +} + +export class FakeRunnable extends Runnable> { + lc_namespace = ["tests", "fake"]; + + returnOptions?: boolean; + + constructor(fields: { returnOptions?: boolean }) { + super(fields); + this.returnOptions = fields.returnOptions; + } + + async invoke( + input: string, + options?: Partial + ): Promise> { + if (this.returnOptions) { + return options ?? {}; + } + return { input }; + } +} + +export class FakeLLM extends LLM { + response?: string; + + thrownErrorString?: string; + + constructor(fields: { response?: string; thrownErrorString?: string }) { + super({}); + this.response = fields.response; + this.thrownErrorString = fields.thrownErrorString; + } + + _llmType() { + return "fake"; + } + + async _call(prompt: string): Promise { + if (this.thrownErrorString) { + throw new Error(this.thrownErrorString); + } + return this.response ?? prompt; + } +} + +export class FakeStreamingLLM extends LLM { + _llmType() { + return "fake"; + } + + async _call(prompt: string): Promise { + return prompt; + } + + async *_streamResponseChunks(input: string) { + for (const c of input) { + await new Promise((resolve) => setTimeout(resolve, 50)); + yield { text: c, generationInfo: {} } as GenerationChunk; + } + } +} + +export class FakeChatModel extends BaseChatModel { + _combineLLMOutput() { + return []; + } + + _llmType(): string { + return "fake"; + } + + async _generate( + messages: BaseMessage[], + options?: this["ParsedCallOptions"] + ): Promise { + if (options?.stop?.length) { + return { + generations: [ + { + message: new AIMessage(options.stop[0]), + text: options.stop[0], + }, + ], + }; + } + const text = messages.map((m) => m.content).join("\n"); + return { + generations: [ + { + message: new AIMessage(text), + text, + }, + ], + llmOutput: {}, + }; + } +} + +export class FakeRetriever extends BaseRetriever { + lc_namespace = ["test", "fake"]; + + output = [ + new Document({ pageContent: "foo" }), + new Document({ pageContent: "bar" }), + ]; + + constructor(fields?: { output: Document[] }) { + super(); + this.output = fields?.output ?? this.output; + } + + async _getRelevantDocuments( + _query: string + // eslint-disable-next-line @typescript-eslint/no-explicit-any + ): Promise>[]> { + return this.output; + } +} + +/** + * Interface for the input parameters specific to the Fake List Chat model. + */ +export interface FakeChatInput extends BaseChatModelParams { + /** Responses to return */ + responses: string[]; + + /** Time to sleep in milliseconds between responses */ + sleep?: number; +} + +/** + * A fake Chat Model that returns a predefined list of responses. It can be used + * for testing purposes. + * @example + * ```typescript + * const chat = new FakeListChatModel({ + * responses: ["I'll callback later.", "You 'console' them!"] + * }); + * + * const firstMessage = new HumanMessage("You want to hear a JavaScript joke?"); + * const secondMessage = new HumanMessage("How do you cheer up a JavaScript developer?"); + * + * // Call the chat model with a message and log the response + * const firstResponse = await chat.call([firstMessage]); + * console.log({ firstResponse }); + * + * const secondResponse = await chat.call([secondMessage]); + * console.log({ secondResponse }); + * ``` + */ +export class FakeListChatModel extends BaseChatModel { + static lc_name() { + return "FakeListChatModel"; + } + + responses: string[]; + + i = 0; + + sleep?: number; + + constructor({ responses, sleep }: FakeChatInput) { + super({}); + this.responses = responses; + this.sleep = sleep; + } + + _combineLLMOutput() { + return []; + } + + _llmType(): string { + return "fake-list"; + } + + async _generate( + _messages: BaseMessage[], + options?: this["ParsedCallOptions"] + ): Promise { + await this._sleepIfRequested(); + + if (options?.stop?.length) { + return { + generations: [this._formatGeneration(options.stop[0])], + }; + } else { + const response = this._currentResponse(); + this._incrementResponse(); + + return { + generations: [this._formatGeneration(response)], + llmOutput: {}, + }; + } + } + + _formatGeneration(text: string) { + return { + message: new AIMessage(text), + text, + }; + } + + async *_streamResponseChunks( + _messages: BaseMessage[], + _options: this["ParsedCallOptions"], + _runManager?: CallbackManagerForLLMRun + ): AsyncGenerator { + const response = this._currentResponse(); + this._incrementResponse(); + + for await (const text of response) { + await this._sleepIfRequested(); + yield this._createResponseChunk(text); + } + } + + async _sleepIfRequested() { + if (this.sleep !== undefined) { + await this._sleep(); + } + } + + async _sleep() { + return new Promise((resolve) => { + setTimeout(() => resolve(), this.sleep); + }); + } + + _createResponseChunk(text: string): ChatGenerationChunk { + return new ChatGenerationChunk({ + message: new AIMessageChunk({ content: text }), + text, + }); + } + + _currentResponse() { + return this.responses[this.i]; + } + + _incrementResponse() { + if (this.i < this.responses.length - 1) { + this.i += 1; + } else { + this.i = 0; + } + } +} diff --git a/langchain-core/src/utils/testing/lib.ts b/langchain-core/src/utils/testing/lib.ts deleted file mode 100644 index 93098720f5ad..000000000000 --- a/langchain-core/src/utils/testing/lib.ts +++ /dev/null @@ -1,146 +0,0 @@ -/* eslint-disable no-promise-executor-return */ -/* eslint-disable @typescript-eslint/no-explicit-any */ - -import { BaseCallbackConfig } from "../../callbacks/manager.js"; -import { Document } from "../../documents/document.js"; -import { BaseChatModel } from "../../language_models/chat_models.js"; -import { LLM } from "../../language_models/llms.js"; -import { BaseMessage, AIMessage } from "../../messages/index.js"; -import { BaseOutputParser } from "../../output_parsers/base.js"; -import { GenerationChunk, type ChatResult } from "../../outputs.js"; -import { BaseRetriever } from "../../retrievers.js"; -import { Runnable } from "../../runnables/base.js"; - -/** - * Parser for comma-separated values. It splits the input text by commas - * and trims the resulting values. - */ -export class FakeSplitIntoListParser extends BaseOutputParser { - lc_namespace = ["tests", "fake"]; - - getFormatInstructions() { - return ""; - } - - async parse(text: string): Promise { - return text.split(",").map((value) => value.trim()); - } -} - -export class FakeRunnable extends Runnable> { - lc_namespace = ["tests", "fake"]; - - returnOptions?: boolean; - - constructor(fields: { returnOptions?: boolean }) { - super(fields); - this.returnOptions = fields.returnOptions; - } - - async invoke( - input: string, - options?: Partial - ): Promise> { - if (this.returnOptions) { - return options ?? {}; - } - return { input }; - } -} - -export class FakeLLM extends LLM { - response?: string; - - thrownErrorString?: string; - - constructor(fields: { response?: string; thrownErrorString?: string }) { - super({}); - this.response = fields.response; - this.thrownErrorString = fields.thrownErrorString; - } - - _llmType() { - return "fake"; - } - - async _call(prompt: string): Promise { - if (this.thrownErrorString) { - throw new Error(this.thrownErrorString); - } - return this.response ?? prompt; - } -} - -export class FakeStreamingLLM extends LLM { - _llmType() { - return "fake"; - } - - async _call(prompt: string): Promise { - return prompt; - } - - async *_streamResponseChunks(input: string) { - for (const c of input) { - await new Promise((resolve) => setTimeout(resolve, 50)); - yield { text: c, generationInfo: {} } as GenerationChunk; - } - } -} - -export class FakeChatModel extends BaseChatModel { - _combineLLMOutput() { - return []; - } - - _llmType(): string { - return "fake"; - } - - async _generate( - messages: BaseMessage[], - options?: this["ParsedCallOptions"] - ): Promise { - if (options?.stop?.length) { - return { - generations: [ - { - message: new AIMessage(options.stop[0]), - text: options.stop[0], - }, - ], - }; - } - const text = messages.map((m) => m.content).join("\n"); - return { - generations: [ - { - message: new AIMessage(text), - text, - }, - ], - llmOutput: {}, - }; - } -} - -export class FakeRetriever extends BaseRetriever { - lc_namespace = ["test", "fake"]; - - output = [ - new Document({ pageContent: "foo" }), - new Document({ pageContent: "bar" }), - ]; - - constructor(fields?: { output: Document[] }) { - super(); - this.output = fields?.output ?? this.output; - } - - async _getRelevantDocuments( - _query: string - // eslint-disable-next-line @typescript-eslint/no-explicit-any - ): Promise>[]> { - return this.output; - } -} From fad95e44ed4afba0d903993145d21dfcc8acdbc8 Mon Sep 17 00:00:00 2001 From: Jacob Lee Date: Fri, 1 Dec 2023 14:05:43 -0800 Subject: [PATCH 03/44] Bump core (#3486) --- langchain-core/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/langchain-core/package.json b/langchain-core/package.json index 49952012139f..33bcbc21692d 100644 --- a/langchain-core/package.json +++ b/langchain-core/package.json @@ -1,6 +1,6 @@ { "name": "@langchain/core", - "version": "0.0.3", + "version": "0.0.4", "description": "Core LangChain.js abstractions and schemas", "type": "module", "engines": { From 9d94bf294ecad79757ba64e3738a16b532338b36 Mon Sep 17 00:00:00 2001 From: Brace Sproul Date: Fri, 1 Dec 2023 17:10:28 -0800 Subject: [PATCH 04/44] langchain[chore]: Remove duplicated code (#3487) * langchain[patch]: Remove duplicated code * yarn install * cr * reinstall * reinstall --- .../integration_llama_cpp_stream_multi.ts | 2 +- .../src/utils/testing}/tests/chatfake.test.ts | 6 +- langchain/package.json | 2 +- langchain/src/chat_models/fake.ts | 143 +----------------- yarn.lock | 4 +- 5 files changed, 11 insertions(+), 146 deletions(-) rename {langchain/src/chat_models => langchain-core/src/utils/testing}/tests/chatfake.test.ts (96%) diff --git a/examples/src/models/chat/integration_llama_cpp_stream_multi.ts b/examples/src/models/chat/integration_llama_cpp_stream_multi.ts index 6277ac69b52b..35633c6b83fe 100644 --- a/examples/src/models/chat/integration_llama_cpp_stream_multi.ts +++ b/examples/src/models/chat/integration_llama_cpp_stream_multi.ts @@ -3,7 +3,7 @@ import { SystemMessage, HumanMessage } from "langchain/schema"; const llamaPath = "/Replace/with/path/to/your/model/gguf-llama2-q4_0.bin"; -const model = new ChatLlamaCpp({ modelPath: llamaPath, temperature: 0.7 }); +const llamaCpp = new ChatLlamaCpp({ modelPath: llamaPath, temperature: 0.7 }); const stream = await llamaCpp.stream([ new SystemMessage( diff --git a/langchain/src/chat_models/tests/chatfake.test.ts b/langchain-core/src/utils/testing/tests/chatfake.test.ts similarity index 96% rename from langchain/src/chat_models/tests/chatfake.test.ts rename to langchain-core/src/utils/testing/tests/chatfake.test.ts index a0b7b8d30ee2..891ddbf0c052 100644 --- a/langchain/src/chat_models/tests/chatfake.test.ts +++ b/langchain-core/src/utils/testing/tests/chatfake.test.ts @@ -1,7 +1,7 @@ import { describe, test, expect, jest } from "@jest/globals"; -import { FakeListChatModel } from "../fake.js"; -import { HumanMessage } from "../../schema/index.js"; -import { StringOutputParser } from "../../schema/output_parser.js"; +import { HumanMessage } from "../../../messages/index.js"; +import { StringOutputParser } from "../../../output_parsers/string.js"; +import { FakeListChatModel } from "../index.js"; describe("Test FakeListChatLLM", () => { test("Should exist", async () => { diff --git a/langchain/package.json b/langchain/package.json index c855ae367c8f..7f7e2888ad6f 100644 --- a/langchain/package.json +++ b/langchain/package.json @@ -1417,7 +1417,7 @@ }, "dependencies": { "@anthropic-ai/sdk": "^0.9.1", - "@langchain/core": "~0.0.3", + "@langchain/core": "~0.0.4", "binary-extensions": "^2.2.0", "expr-eval": "^2.0.2", "flat": "^5.0.2", diff --git a/langchain/src/chat_models/fake.ts b/langchain/src/chat_models/fake.ts index 3b075f98ed5a..a155a34e74fa 100644 --- a/langchain/src/chat_models/fake.ts +++ b/langchain/src/chat_models/fake.ts @@ -1,139 +1,4 @@ -import { BaseChatModel, BaseChatModelParams } from "./base.js"; -import { - AIMessage, - AIMessageChunk, - BaseMessage, - ChatGenerationChunk, - ChatResult, -} from "../schema/index.js"; -import { CallbackManagerForLLMRun } from "../callbacks/manager.js"; - -/** - * Interface for the input parameters specific to the Fake List Chat model. - */ -export interface FakeChatInput extends BaseChatModelParams { - /** Responses to return */ - responses: string[]; - - /** Time to sleep in milliseconds between responses */ - sleep?: number; -} - -/** - * A fake Chat Model that returns a predefined list of responses. It can be used - * for testing purposes. - * @example - * ```typescript - * const chat = new FakeListChatModel({ - * responses: ["I'll callback later.", "You 'console' them!"] - * }); - * - * const firstMessage = new HumanMessage("You want to hear a JavaScript joke?"); - * const secondMessage = new HumanMessage("How do you cheer up a JavaScript developer?"); - * - * // Call the chat model with a message and log the response - * const firstResponse = await chat.call([firstMessage]); - * console.log({ firstResponse }); - * - * const secondResponse = await chat.call([secondMessage]); - * console.log({ secondResponse }); - * ``` - */ -export class FakeListChatModel extends BaseChatModel { - static lc_name() { - return "FakeListChatModel"; - } - - responses: string[]; - - i = 0; - - sleep?: number; - - constructor({ responses, sleep }: FakeChatInput) { - super({}); - this.responses = responses; - this.sleep = sleep; - } - - _combineLLMOutput() { - return []; - } - - _llmType(): string { - return "fake-list"; - } - - async _generate( - _messages: BaseMessage[], - options?: this["ParsedCallOptions"] - ): Promise { - await this._sleepIfRequested(); - - if (options?.stop?.length) { - return { - generations: [this._formatGeneration(options.stop[0])], - }; - } else { - const response = this._currentResponse(); - this._incrementResponse(); - - return { - generations: [this._formatGeneration(response)], - llmOutput: {}, - }; - } - } - - _formatGeneration(text: string) { - return { - message: new AIMessage(text), - text, - }; - } - - async *_streamResponseChunks( - _messages: BaseMessage[], - _options: this["ParsedCallOptions"], - _runManager?: CallbackManagerForLLMRun - ): AsyncGenerator { - const response = this._currentResponse(); - this._incrementResponse(); - - for await (const text of response) { - await this._sleepIfRequested(); - yield this._createResponseChunk(text); - } - } - - async _sleepIfRequested() { - if (this.sleep !== undefined) { - await this._sleep(); - } - } - - async _sleep() { - return new Promise((resolve) => { - setTimeout(() => resolve(), this.sleep); - }); - } - - _createResponseChunk(text: string): ChatGenerationChunk { - return new ChatGenerationChunk({ - message: new AIMessageChunk({ content: text }), - text, - }); - } - - _currentResponse() { - return this.responses[this.i]; - } - - _incrementResponse() { - if (this.i < this.responses.length - 1) { - this.i += 1; - } else { - this.i = 0; - } - } -} +export { + type FakeChatInput, + FakeListChatModel, +} from "@langchain/core/utils/testing"; diff --git a/yarn.lock b/yarn.lock index f9beb04d62dd..f61727ea5e07 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7997,7 +7997,7 @@ __metadata: languageName: unknown linkType: soft -"@langchain/core@workspace:*, @langchain/core@workspace:langchain-core, @langchain/core@~0.0.3": +"@langchain/core@workspace:*, @langchain/core@workspace:langchain-core, @langchain/core@~0.0.4": version: 0.0.0-use.local resolution: "@langchain/core@workspace:langchain-core" dependencies: @@ -22607,7 +22607,7 @@ __metadata: "@gradientai/nodejs-sdk": ^1.2.0 "@huggingface/inference": ^2.6.4 "@jest/globals": ^29.5.0 - "@langchain/core": ~0.0.3 + "@langchain/core": ~0.0.4 "@mozilla/readability": ^0.4.4 "@notionhq/client": ^2.2.10 "@opensearch-project/opensearch": ^2.2.0 From dae9f2fc09a082fe3c6a725fb08ec542f9740ec6 Mon Sep 17 00:00:00 2001 From: Jacob Lee Date: Fri, 1 Dec 2023 17:20:53 -0800 Subject: [PATCH 05/44] Adds core tests to CI (#3489) --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index dc685f12c715..af0779770ea1 100644 --- a/package.json +++ b/package.json @@ -25,7 +25,7 @@ "lint": "turbo run lint --concurrency 1", "lint:fix": "yarn lint -- --fix", "test": "yarn test:unit && yarn workspace @langchain/core build && yarn workspace langchain build && yarn test:exports:docker", - "test:unit": "turbo run test --filter langchain", + "test:unit": "turbo run test --filter @langchain/core --filter langchain", "test:int": "yarn run test:int:deps && turbo run test:integration ; yarn run test:int:deps:down", "test:int:deps": "docker compose -f test-int-deps-docker-compose.yml up -d", "test:int:deps:down": "docker compose -f test-int-deps-docker-compose.yml down", From 2cc99513d0055152adc3644840109891be014c33 Mon Sep 17 00:00:00 2001 From: Jacob Lee Date: Fri, 1 Dec 2023 17:33:07 -0800 Subject: [PATCH 06/44] Adds missing export (#3490) --- langchain/src/agents/index.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/langchain/src/agents/index.ts b/langchain/src/agents/index.ts index 4cba80d5c171..22104394f290 100644 --- a/langchain/src/agents/index.ts +++ b/langchain/src/agents/index.ts @@ -2,6 +2,8 @@ export { Agent, type AgentArgs, BaseSingleActionAgent, + BaseMultiActionAgent, + RunnableAgent, LLMSingleActionAgent, type LLMSingleActionAgentInput, type OutputParserArgs, From 2e32f7cf65b61739fa71402330945b094b855b62 Mon Sep 17 00:00:00 2001 From: Jacob Lee Date: Fri, 1 Dec 2023 17:47:17 -0800 Subject: [PATCH 07/44] Catch tiktoken errors (#3491) --- langchain-core/src/language_models/base.ts | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/langchain-core/src/language_models/base.ts b/langchain-core/src/language_models/base.ts index a09e3d40444c..458697430198 100644 --- a/langchain-core/src/language_models/base.ts +++ b/langchain-core/src/language_models/base.ts @@ -331,10 +331,15 @@ export abstract class BaseLanguageModel< error ); } - } - - if (this._encoding) { - numTokens = this._encoding.encode(content).length; + } else { + try { + numTokens = this._encoding.encode(content).length; + } catch (error) { + console.warn( + "Failed to calculate number of tokens, falling back to approximate count", + error + ); + } } return numTokens; From 266e362a06a79417c4b87211e56334dd97adf580 Mon Sep 17 00:00:00 2001 From: Shareef P Date: Sat, 2 Dec 2023 07:17:34 +0530 Subject: [PATCH 08/44] langchain[patch]: onToken event added in ChatLlamaCpp call function (#3443) * onToken added in lama cpp * package update * repository username * Drop _ in _options * onToken in llama_cpp llm --------- Co-authored-by: Brace Sproul --- langchain/src/chat_models/llama_cpp.ts | 3 ++- langchain/src/llms/llama_cpp.ts | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/langchain/src/chat_models/llama_cpp.ts b/langchain/src/chat_models/llama_cpp.ts index 9eec7ec3c873..3df8b0d2a3c9 100644 --- a/langchain/src/chat_models/llama_cpp.ts +++ b/langchain/src/chat_models/llama_cpp.ts @@ -116,7 +116,7 @@ export class ChatLlamaCpp extends SimpleChatModel { /** @ignore */ async _call( messages: BaseMessage[], - _options: this["ParsedCallOptions"] + options: this["ParsedCallOptions"] ): Promise { let prompt = ""; @@ -137,6 +137,7 @@ export class ChatLlamaCpp extends SimpleChatModel { try { const promptOptions = { + onToken: options.onToken, maxTokens: this?.maxTokens, temperature: this?.temperature, topK: this?.topK, diff --git a/langchain/src/llms/llama_cpp.ts b/langchain/src/llms/llama_cpp.ts index 5081bde04008..f2d6518ffd2b 100644 --- a/langchain/src/llms/llama_cpp.ts +++ b/langchain/src/llms/llama_cpp.ts @@ -72,10 +72,11 @@ export class LlamaCpp extends LLM { /** @ignore */ async _call( prompt: string, - _options?: this["ParsedCallOptions"] + options?: this["ParsedCallOptions"] ): Promise { try { const promptOptions = { + onToken: options?.onToken, maxTokens: this?.maxTokens, temperature: this?.temperature, topK: this?.topK, From 757a3ce9606360910b92b87b38da14c97840c747 Mon Sep 17 00:00:00 2001 From: Jacob Lee Date: Fri, 1 Dec 2023 18:36:45 -0800 Subject: [PATCH 09/44] Fix build (#3492) --- langchain/src/chat_models/minimax.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/langchain/src/chat_models/minimax.ts b/langchain/src/chat_models/minimax.ts index 31735a1b9ab5..c0e521d6fec0 100644 --- a/langchain/src/chat_models/minimax.ts +++ b/langchain/src/chat_models/minimax.ts @@ -1,4 +1,4 @@ -import type { OpenAIClient } from "@langchain/openai"; +import type { OpenAI as OpenAIClient } from "openai"; import { BaseChatModel, BaseChatModelParams } from "./base.js"; import { From 07e6c4840829137a1b23160fc16771fa75c1e9f5 Mon Sep 17 00:00:00 2001 From: Brace Sproul Date: Fri, 1 Dec 2023 18:58:31 -0800 Subject: [PATCH 10/44] core[minor]: Runnable with message history (#3437) * Runnable with message history * cr * cr * adds withListeners method to runnables/callbacks * added entrypoint for root listener file * cr * cr * cr * cr * cr * support async listeners * allow for run or run and config as args to listener funcs * cr * chore: lint files * cr * cr * eslint disbale any * update types * cr * cr * cr * cr * cr * cr * Style --------- Co-authored-by: jacoblee93 --- langchain-core/src/callbacks/manager.ts | 33 ++- langchain-core/src/chat_history.ts | 30 +++ langchain-core/src/runnables/base.ts | 181 ++++++++++++++--- langchain-core/src/runnables/config.ts | 78 +++++++ langchain-core/src/runnables/history.ts | 190 ++++++++++++++++++ .../src/runnables/tests/runnable.test.ts | 87 +++++++- .../runnables/tests/runnable_history.test.ts | 47 +++++ langchain-core/src/tracers/root_listener.ts | 90 +++++++++ .../toolkits/conversational_retrieval/tool.ts | 2 +- langchain/src/memory/vector_store.ts | 2 +- langchain/src/tools/webbrowser.ts | 2 +- langchain/src/util/document.ts | 6 +- 12 files changed, 697 insertions(+), 51 deletions(-) create mode 100644 langchain-core/src/runnables/history.ts create mode 100644 langchain-core/src/runnables/tests/runnable_history.test.ts create mode 100644 langchain-core/src/tracers/root_listener.ts diff --git a/langchain-core/src/callbacks/manager.ts b/langchain-core/src/callbacks/manager.ts index 34fe086636a6..baeccb445618 100644 --- a/langchain-core/src/callbacks/manager.ts +++ b/langchain-core/src/callbacks/manager.ts @@ -61,6 +61,13 @@ export interface BaseCallbackConfig { * Tags are passed to all callbacks, metadata is passed to handle*Start callbacks. */ callbacks?: Callbacks; + + /** + * Runtime values for attributes previously made configurable on this Runnable, + * or sub-Runnables. + */ + // eslint-disable-next-line @typescript-eslint/no-explicit-any + configurable?: Record; } export function parseCallbackConfigArg( @@ -484,9 +491,9 @@ export class CallbackManager extends BaseCallbackManager implements BaseCallbackManagerMethods { - handlers: BaseCallbackHandler[]; + handlers: BaseCallbackHandler[] = []; - inheritableHandlers: BaseCallbackHandler[]; + inheritableHandlers: BaseCallbackHandler[] = []; tags: string[] = []; @@ -500,10 +507,26 @@ export class CallbackManager private readonly _parentRunId?: string; - constructor(parentRunId?: string) { + constructor( + parentRunId?: string, + options?: { + handlers?: BaseCallbackHandler[]; + inheritableHandlers?: BaseCallbackHandler[]; + tags?: string[]; + inheritableTags?: string[]; + metadata?: Record; + inheritableMetadata?: Record; + } + ) { super(); - this.handlers = []; - this.inheritableHandlers = []; + this.handlers = options?.handlers ?? this.handlers; + this.inheritableHandlers = + options?.inheritableHandlers ?? this.inheritableHandlers; + this.tags = options?.tags ?? this.tags; + this.inheritableTags = options?.inheritableTags ?? this.inheritableTags; + this.metadata = options?.metadata ?? this.metadata; + this.inheritableMetadata = + options?.inheritableMetadata ?? this.inheritableMetadata; this._parentRunId = parentRunId; } diff --git a/langchain-core/src/chat_history.ts b/langchain-core/src/chat_history.ts index 6841d19e865e..979323008b3b 100644 --- a/langchain-core/src/chat_history.ts +++ b/langchain-core/src/chat_history.ts @@ -32,3 +32,33 @@ export abstract class BaseListChatMessageHistory extends Serializable { return this.addMessage(new AIMessage(message)); } } + +export class FakeChatMessageHistory extends BaseChatMessageHistory { + lc_namespace = ["langchain", "core", "message", "fake"]; + + messages: Array = []; + + constructor() { + super(); + } + + async getMessages(): Promise { + return this.messages; + } + + async addMessage(message: BaseMessage): Promise { + this.messages.push(message); + } + + async addUserMessage(message: string): Promise { + this.messages.push(new HumanMessage(message)); + } + + async addAIChatMessage(message: string): Promise { + this.messages.push(new AIMessage(message)); + } + + async clear(): Promise { + this.messages = []; + } +} diff --git a/langchain-core/src/runnables/base.ts b/langchain-core/src/runnables/base.ts index 0403b91486bb..697bc59f095a 100644 --- a/langchain-core/src/runnables/base.ts +++ b/langchain-core/src/runnables/base.ts @@ -12,11 +12,21 @@ import { } from "../tracers/log_stream.js"; import { Serializable } from "../load/serializable.js"; import { IterableReadableStream } from "../utils/stream.js"; -import { RunnableConfig, getCallbackMangerForConfig } from "./config.js"; +import { + RunnableConfig, + getCallbackMangerForConfig, + mergeConfigs, +} from "./config.js"; import { AsyncCaller } from "../utils/async_caller.js"; +import { Run } from "../tracers/base.js"; +import { RootListenersTracer } from "../tracers/root_listener.js"; export type RunnableFunc = ( - input: RunInput + input: RunInput, + // eslint-disable-next-line @typescript-eslint/no-explicit-any + options?: Record & { + config?: RunnableConfig; + } ) => RunOutput | Promise; // eslint-disable-next-line @typescript-eslint/no-explicit-any @@ -549,6 +559,45 @@ export abstract class Runnable< static isRunnable(thing: any): thing is Runnable { return thing ? thing.lc_runnable : false; } + + /** + * Bind lifecycle listeners to a Runnable, returning a new Runnable. + * The Run object contains information about the run, including its id, + * type, input, output, error, startTime, endTime, and any tags or metadata + * added to the run. + * + * @param {Object} params - The object containing the callback functions. + * @param {(run: Run) => void} params.onStart - Called before the runnable starts running, with the Run object. + * @param {(run: Run) => void} params.onEnd - Called after the runnable finishes running, with the Run object. + * @param {(run: Run) => void} params.onError - Called if the runnable throws an error, with the Run object. + */ + withListeners({ + onStart, + onEnd, + onError, + }: { + onStart?: (run: Run, config?: RunnableConfig) => void | Promise; + onEnd?: (run: Run, config?: RunnableConfig) => void | Promise; + onError?: (run: Run, config?: RunnableConfig) => void | Promise; + }): Runnable { + // eslint-disable-next-line @typescript-eslint/no-use-before-define + return new RunnableBinding({ + bound: this, + config: {}, + configFactories: [ + (config) => ({ + callbacks: [ + new RootListenersTracer({ + config, + onStart, + onEnd, + onError, + }), + ], + }), + ], + }); + } } export type RunnableBindingArgs< @@ -557,8 +606,9 @@ export type RunnableBindingArgs< CallOptions extends RunnableConfig > = { bound: Runnable; - kwargs: Partial; + kwargs?: Partial; config: RunnableConfig; + configFactories?: Array<(config: RunnableConfig) => RunnableConfig>; }; /** @@ -581,31 +631,35 @@ export class RunnableBinding< config: RunnableConfig; - protected kwargs: Partial; + protected kwargs?: Partial; + + configFactories?: Array< + (config: RunnableConfig) => RunnableConfig | Promise + >; constructor(fields: RunnableBindingArgs) { super(fields); this.bound = fields.bound; this.kwargs = fields.kwargs; this.config = fields.config; + this.configFactories = fields.configFactories; } - // eslint-disable-next-line @typescript-eslint/no-explicit-any - _mergeConfig(options?: Record) { + async _mergeConfig( // eslint-disable-next-line @typescript-eslint/no-explicit-any - const copy: Record = { ...this.config }; - if (options) { - for (const key of Object.keys(options)) { - if (key === "metadata") { - copy[key] = { ...copy[key], ...options[key] }; - } else if (key === "tags") { - copy[key] = (copy[key] ?? []).concat(options[key] ?? []); - } else { - copy[key] = options[key] ?? copy[key]; - } - } - } - return copy as Partial; + options?: Record + ): Promise> { + const config = mergeConfigs(this.config, options); + return mergeConfigs( + config, + ...(this.configFactories + ? await Promise.all( + this.configFactories.map( + async (configFactory) => await configFactory(config) + ) + ) + : []) + ); } bind( @@ -645,7 +699,7 @@ export class RunnableBinding< ): Promise { return this.bound.invoke( input, - this._mergeConfig({ ...options, ...this.kwargs }) + await this._mergeConfig({ ...options, ...this.kwargs }) ); } @@ -673,13 +727,15 @@ export class RunnableBinding< batchOptions?: RunnableBatchOptions ): Promise<(RunOutput | Error)[]> { const mergedOptions = Array.isArray(options) - ? options.map((individualOption) => - this._mergeConfig({ - ...individualOption, - ...this.kwargs, - }) + ? await Promise.all( + options.map(async (individualOption) => + this._mergeConfig({ + ...individualOption, + ...this.kwargs, + }) + ) ) - : this._mergeConfig({ ...options, ...this.kwargs }); + : await this._mergeConfig({ ...options, ...this.kwargs }); return this.bound.batch(inputs, mergedOptions, batchOptions); } @@ -689,7 +745,7 @@ export class RunnableBinding< ) { yield* this.bound._streamIterator( input, - this._mergeConfig({ ...options, ...this.kwargs }) + await this._mergeConfig({ ...options, ...this.kwargs }) ); } @@ -699,7 +755,7 @@ export class RunnableBinding< ): Promise> { return this.bound.stream( input, - this._mergeConfig({ ...options, ...this.kwargs }) + await this._mergeConfig({ ...options, ...this.kwargs }) ); } @@ -710,7 +766,7 @@ export class RunnableBinding< ): AsyncGenerator { yield* this.bound.transform( generator, - this._mergeConfig({ ...options, ...this.kwargs }) + await this._mergeConfig({ ...options, ...this.kwargs }) ); } @@ -721,6 +777,45 @@ export class RunnableBinding< ): thing is RunnableBinding { return thing.bound && Runnable.isRunnable(thing.bound); } + + /** + * Bind lifecycle listeners to a Runnable, returning a new Runnable. + * The Run object contains information about the run, including its id, + * type, input, output, error, startTime, endTime, and any tags or metadata + * added to the run. + * + * @param {Object} params - The object containing the callback functions. + * @param {(run: Run) => void} params.onStart - Called before the runnable starts running, with the Run object. + * @param {(run: Run) => void} params.onEnd - Called after the runnable finishes running, with the Run object. + * @param {(run: Run) => void} params.onError - Called if the runnable throws an error, with the Run object. + */ + withListeners({ + onStart, + onEnd, + onError, + }: { + onStart?: (run: Run, config?: RunnableConfig) => void | Promise; + onEnd?: (run: Run, config?: RunnableConfig) => void | Promise; + onError?: (run: Run, config?: RunnableConfig) => void | Promise; + }): Runnable { + return new RunnableBinding({ + bound: this.bound, + kwargs: this.kwargs, + config: this.config, + configFactories: [ + (config) => ({ + callbacks: [ + new RootListenersTracer({ + config, + onStart, + onEnd, + onError, + }), + ], + }), + ], + }); + } } /** @@ -789,6 +884,32 @@ export class RunnableEach< this._patchConfig(config, runManager?.getChild()) ); } + + /** + * Bind lifecycle listeners to a Runnable, returning a new Runnable. + * The Run object contains information about the run, including its id, + * type, input, output, error, startTime, endTime, and any tags or metadata + * added to the run. + * + * @param {Object} params - The object containing the callback functions. + * @param {(run: Run) => void} params.onStart - Called before the runnable starts running, with the Run object. + * @param {(run: Run) => void} params.onEnd - Called after the runnable finishes running, with the Run object. + * @param {(run: Run) => void} params.onError - Called if the runnable throws an error, with the Run object. + */ + withListeners({ + onStart, + onEnd, + onError, + }: { + onStart?: (run: Run, config?: RunnableConfig) => void | Promise; + onEnd?: (run: Run, config?: RunnableConfig) => void | Promise; + onError?: (run: Run, config?: RunnableConfig) => void | Promise; + // eslint-disable-next-line @typescript-eslint/no-explicit-any + }): Runnable { + return new RunnableEach({ + bound: this.bound.withListeners({ onStart, onEnd, onError }), + }); + } } /** @@ -1382,7 +1503,7 @@ export class RunnableLambda extends Runnable< config?: Partial, runManager?: CallbackManagerForChainRun ) { - let output = await this.func(input); + let output = await this.func(input, { config }); if (output && Runnable.isRunnable(output)) { output = await output.invoke( input, diff --git a/langchain-core/src/runnables/config.ts b/langchain-core/src/runnables/config.ts index d70cbeb328bc..e604071a7b09 100644 --- a/langchain-core/src/runnables/config.ts +++ b/langchain-core/src/runnables/config.ts @@ -14,3 +14,81 @@ export async function getCallbackMangerForConfig(config?: RunnableConfig) { config?.metadata ); } + +export function mergeConfigs( + config: RunnableConfig, + // eslint-disable-next-line @typescript-eslint/no-explicit-any + options?: Record +): Partial { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const copy: Record = { ...config }; + if (options) { + for (const key of Object.keys(options)) { + if (key === "metadata") { + copy[key] = { ...copy[key], ...options[key] }; + } else if (key === "tags") { + copy[key] = (copy[key] ?? []).concat(options[key] ?? []); + } else if (key === "callbacks") { + const baseCallbacks = copy.callbacks; + const providedCallbacks = options.callbacks ?? config.callbacks; + // callbacks can be either undefined, Array or manager + // so merging two callbacks values has 6 cases + if (Array.isArray(providedCallbacks)) { + if (!baseCallbacks) { + copy.callbacks = providedCallbacks; + } else if (Array.isArray(baseCallbacks)) { + copy.callbacks = baseCallbacks.concat(providedCallbacks); + } else { + // baseCallbacks is a manager + const manager = baseCallbacks.copy(); + for (const callback of providedCallbacks) { + manager.addHandler(callback, true); + } + copy.callbacks = manager; + } + } else if (providedCallbacks) { + // providedCallbacks is a manager + if (!baseCallbacks) { + copy.callbacks = providedCallbacks; + } else if (Array.isArray(baseCallbacks)) { + const manager = providedCallbacks.copy(); + for (const callback of baseCallbacks) { + manager.addHandler(callback, true); + } + copy.callbacks = manager; + } else { + // baseCallbacks is also a manager + copy.callbacks = new CallbackManager( + providedCallbacks.parentRunId, + { + handlers: baseCallbacks.handlers.concat( + providedCallbacks.handlers + ), + inheritableHandlers: baseCallbacks.inheritableHandlers.concat( + providedCallbacks.inheritableHandlers + ), + tags: Array.from( + new Set(baseCallbacks.tags.concat(providedCallbacks.tags)) + ), + inheritableTags: Array.from( + new Set( + baseCallbacks.inheritableTags.concat( + providedCallbacks.inheritableTags + ) + ) + ), + metadata: { + ...baseCallbacks.metadata, + ...providedCallbacks.metadata, + }, + } + ); + } + } + } else { + copy[key] = options[key] ?? copy[key]; + } + } + } + return copy as Partial; +} diff --git a/langchain-core/src/runnables/history.ts b/langchain-core/src/runnables/history.ts new file mode 100644 index 000000000000..662a1cbb4de6 --- /dev/null +++ b/langchain-core/src/runnables/history.ts @@ -0,0 +1,190 @@ +import { BaseCallbackConfig } from "../callbacks/manager.js"; +import { BaseChatMessageHistory } from "../chat_history.js"; +import { + AIMessage, + BaseMessage, + HumanMessage, + isBaseMessage, +} from "../messages/index.js"; +import { Run } from "../tracers/base.js"; +import { + Runnable, + RunnableBinding, + type RunnableBindingArgs, + RunnableLambda, +} from "./base.js"; +import { RunnableConfig } from "./config.js"; +import { RunnablePassthrough } from "./passthrough.js"; + +type GetSessionHistoryCallable = ( + // eslint-disable-next-line @typescript-eslint/no-explicit-any + ...args: Array +) => Promise; + +export class RunnableWithMessageHistory< + RunInput, + RunOutput +> extends RunnableBinding { + runnable: Runnable; + + inputMessagesKey?: string; + + outputMessagesKey?: string; + + historyMessagesKey?: string; + + getMessageHistory: GetSessionHistoryCallable; + + constructor( + fields: Omit< + RunnableBindingArgs, + "bound" + > & { + runnable: Runnable; + getMessageHistory: GetSessionHistoryCallable; + inputMessagesKey?: string; + outputMessagesKey?: string; + historyMessagesKey?: string; + } + ) { + let historyChain: Runnable = new RunnableLambda({ + func: (input, options) => this._enterHistory(input, options ?? {}), + }).withConfig({ runName: "loadHistory" }); + + const messagesKey = fields.historyMessagesKey ?? fields.inputMessagesKey; + if (messagesKey) { + historyChain = RunnablePassthrough.assign({ + [messagesKey]: historyChain, + }).withConfig({ runName: "insertHistory" }); + } + + const bound = historyChain + .pipe( + fields.runnable.withListeners({ + onEnd: (run, config) => this._exitHistory(run, config ?? {}), + }) + ) + .withConfig({ runName: "RunnableWithMessageHistory" }); + + super({ + ...fields, + bound, + }); + this.runnable = fields.runnable; + this.getMessageHistory = fields.getMessageHistory; + this.inputMessagesKey = fields.inputMessagesKey; + this.outputMessagesKey = fields.outputMessagesKey; + this.historyMessagesKey = fields.historyMessagesKey; + } + + _getInputMessages( + inputValue: string | BaseMessage | Array + ): Array { + if (typeof inputValue === "string") { + return [new HumanMessage(inputValue)]; + } else if (Array.isArray(inputValue)) { + return inputValue; + } else { + return [inputValue]; + } + } + + _getOutputMessages( + // eslint-disable-next-line @typescript-eslint/no-explicit-any + outputValue: string | BaseMessage | Array | Record + ): Array { + let newOutputValue = outputValue; + if ( + !Array.isArray(outputValue) && + !isBaseMessage(outputValue) && + typeof outputValue !== "string" + ) { + newOutputValue = outputValue[this.outputMessagesKey ?? "output"]; + } + + if (typeof newOutputValue === "string") { + return [new AIMessage(newOutputValue)]; + } else if (Array.isArray(newOutputValue)) { + return newOutputValue; + } else if (isBaseMessage(newOutputValue)) { + return [newOutputValue]; + } + throw new Error( + `Expected a string, BaseMessage, or array of BaseMessages. Received: ${JSON.stringify( + newOutputValue, + null, + 2 + )}` + ); + } + + _enterHistory( + // eslint-disable-next-line @typescript-eslint/no-explicit-any + input: any, + kwargs?: { config?: RunnableConfig } + ): Array { + const history = kwargs?.config?.configurable?.messageHistory; + + if (this.historyMessagesKey) { + return history.messages; + } + + const inputVal = + input || + (this.inputMessagesKey ? input[this.inputMessagesKey] : undefined); + const historyMessages = history ? history.messages : []; + const returnType = [ + ...historyMessages, + ...this._getInputMessages(inputVal), + ]; + return returnType; + } + + async _exitHistory(run: Run, config: BaseCallbackConfig): Promise { + const history = config.configurable?.messageHistory; + + // Get input messages + const { inputs } = run; + const inputValue = inputs[this.inputMessagesKey ?? "input"]; + const inputMessages = this._getInputMessages(inputValue); + // Get output messages + const outputValue = run.outputs; + if (!outputValue) { + throw new Error( + `Output values from 'Run' undefined. Run: ${JSON.stringify( + run, + null, + 2 + )}` + ); + } + const outputMessages = this._getOutputMessages(outputValue); + + for await (const message of [...inputMessages, ...outputMessages]) { + await history.addMessage(message); + } + } + + async _mergeConfig(...configs: Array) { + const config = await super._mergeConfig(...configs); + // Extract sessionId + if (!config.configurable || !config.configurable.sessionId) { + const exampleInput = { + [this.inputMessagesKey ?? "input"]: "foo", + }; + const exampleConfig = { configurable: { sessionId: "123" } }; + throw new Error( + `sessionId is required. Pass it in as part of the config argument to .invoke() or .stream()\n` + + `eg. chain.invoke(${JSON.stringify(exampleInput)}, ${JSON.stringify( + exampleConfig + )})` + ); + } + // attach messageHistory + const { sessionId } = config.configurable; + config.configurable.messageHistory = await this.getMessageHistory( + sessionId + ); + return config; + } +} diff --git a/langchain-core/src/runnables/tests/runnable.test.ts b/langchain-core/src/runnables/tests/runnable.test.ts index 702e2a309193..149a39112c4f 100644 --- a/langchain-core/src/runnables/tests/runnable.test.ts +++ b/langchain-core/src/runnables/tests/runnable.test.ts @@ -1,16 +1,18 @@ /* eslint-disable no-promise-executor-return */ /* eslint-disable @typescript-eslint/no-explicit-any */ -import { test } from "@jest/globals"; +import { Run } from "langsmith"; +import { jest } from "@jest/globals"; +import { createChatMessageChunkEncoderStream } from "../../language_models/chat_models.js"; +import { BaseMessage } from "../../messages/index.js"; +import { OutputParserException } from "../../output_parsers/base.js"; +import { StringOutputParser } from "../../output_parsers/string.js"; import { ChatPromptTemplate, - HumanMessagePromptTemplate, - PromptTemplate, SystemMessagePromptTemplate, -} from "../../prompts/index.js"; -import { Document } from "../../documents/document.js"; -import { createChatMessageChunkEncoderStream } from "../../language_models/chat_models.js"; -import { StringOutputParser } from "../../output_parsers/string.js"; + HumanMessagePromptTemplate, +} from "../../prompts/chat.js"; +import { PromptTemplate } from "../../prompts/prompt.js"; import { FakeLLM, FakeChatModel, @@ -18,11 +20,11 @@ import { FakeStreamingLLM, FakeSplitIntoListParser, FakeRunnable, + FakeListChatModel, } from "../../utils/testing/index.js"; import { RunnableSequence, RunnableMap, RunnableLambda } from "../base.js"; import { RouterRunnable } from "../router.js"; -import { OutputParserException } from "../../output_parsers/base.js"; -import { BaseMessage } from "../../messages/index.js"; +import { Document } from "../../documents/document.js"; test("Test batch", async () => { const llm = new FakeLLM({}); @@ -215,6 +217,73 @@ test("Runnable withConfig", async () => { expect(chunks[0]?.metadata).toEqual({ a: "updated", b: "c" }); }); +test("Listeners work", async () => { + const prompt = ChatPromptTemplate.fromMessages([ + SystemMessagePromptTemplate.fromTemplate("You are a nice assistant."), + ["human", "{question}"], + ]); + const model = new FakeListChatModel({ + responses: ["foo"], + }); + const chain = prompt.pipe(model); + + const mockStart = jest.fn(); + const mockEnd = jest.fn(); + + await chain + .withListeners({ + onStart: (run: Run) => { + mockStart(run); + }, + onEnd: (run: Run) => { + mockEnd(run); + }, + }) + .invoke({ question: "What is the meaning of life?" }); + + expect(mockStart).toHaveBeenCalledTimes(1); + expect((mockStart.mock.calls[0][0] as { name: string }).name).toBe( + "RunnableSequence" + ); + expect(mockEnd).toHaveBeenCalledTimes(1); +}); + +test("Listeners work with async handlers", async () => { + const prompt = ChatPromptTemplate.fromMessages([ + SystemMessagePromptTemplate.fromTemplate("You are a nice assistant."), + ["human", "{question}"], + ]); + const model = new FakeListChatModel({ + responses: ["foo"], + }); + const chain = prompt.pipe(model); + + const mockStart = jest.fn(); + const mockEnd = jest.fn(); + + await chain + .withListeners({ + onStart: async (run: Run) => { + const promise = new Promise((resolve) => setTimeout(resolve, 2000)); + await promise; + mockStart(run); + }, + // eslint-disable-next-line @typescript-eslint/no-misused-promises + onEnd: async (run: Run) => { + const promise = new Promise((resolve) => setTimeout(resolve, 2000)); + await promise; + mockEnd(run); + }, + }) + .invoke({ question: "What is the meaning of life?" }); + + expect(mockStart).toHaveBeenCalledTimes(1); + expect((mockStart.mock.calls[0][0] as { name: string }).name).toBe( + "RunnableSequence" + ); + expect(mockEnd).toHaveBeenCalledTimes(1); +}); + test("Create a runnable sequence and run it", async () => { const promptTemplate = PromptTemplate.fromTemplate("{input}"); const llm = new FakeChatModel({}); diff --git a/langchain-core/src/runnables/tests/runnable_history.test.ts b/langchain-core/src/runnables/tests/runnable_history.test.ts new file mode 100644 index 000000000000..c6e5d1822e63 --- /dev/null +++ b/langchain-core/src/runnables/tests/runnable_history.test.ts @@ -0,0 +1,47 @@ +import { BaseMessage, HumanMessage } from "../../messages/index.js"; +import { RunnableLambda } from "../base.js"; +import { RunnableConfig } from "../config.js"; +import { RunnableWithMessageHistory } from "../history.js"; +import { + BaseChatMessageHistory, + FakeChatMessageHistory, +} from "../../chat_history.js"; + +async function getGetSessionHistory(): Promise< + (sessionId: string) => Promise +> { + const chatHistoryStore: { [key: string]: BaseChatMessageHistory } = {}; + + async function getSessionHistory( + sessionId: string + ): Promise { + if (!(sessionId in chatHistoryStore)) { + chatHistoryStore[sessionId] = new FakeChatMessageHistory(); + } + return chatHistoryStore[sessionId]; + } + + return getSessionHistory; +} + +test("Runnable with message history", async () => { + const runnable = new RunnableLambda({ + func: (messages: BaseMessage[]) => + `you said: ${messages + .filter((m) => m._getType() === "human") + .map((m) => m.content) + .join("\n")}`, + }); + + const getMessageHistory = await getGetSessionHistory(); + const withHistory = new RunnableWithMessageHistory({ + runnable, + config: {}, + getMessageHistory, + }); + const config: RunnableConfig = { configurable: { sessionId: "1" } }; + let output = await withHistory.invoke([new HumanMessage("hello")], config); + expect(output).toBe("you said: hello"); + output = await withHistory.invoke([new HumanMessage("good bye")], config); + expect(output).toBe("you said: hello\ngood bye"); +}); diff --git a/langchain-core/src/tracers/root_listener.ts b/langchain-core/src/tracers/root_listener.ts new file mode 100644 index 000000000000..222c307f531f --- /dev/null +++ b/langchain-core/src/tracers/root_listener.ts @@ -0,0 +1,90 @@ +import { RunnableConfig } from "../runnables/config.js"; +import { BaseTracer, Run } from "./base.js"; + +export class RootListenersTracer extends BaseTracer { + name = "RootListenersTracer"; + + /** The Run's ID. Type UUID */ + rootId?: string; + + config: RunnableConfig; + + argOnStart?: { + (run: Run): void | Promise; + (run: Run, config: RunnableConfig): void | Promise; + }; + + argOnEnd?: { + (run: Run): void | Promise; + (run: Run, config: RunnableConfig): void | Promise; + }; + + argOnError?: { + (run: Run): void | Promise; + (run: Run, config: RunnableConfig): void | Promise; + }; + + constructor({ + config, + onStart, + onEnd, + onError, + }: { + config: RunnableConfig; + onStart?: (run: Run, config?: RunnableConfig) => void | Promise; + onEnd?: (run: Run, config?: RunnableConfig) => void | Promise; + onError?: (run: Run, config?: RunnableConfig) => void | Promise; + }) { + super(); + this.config = config; + this.argOnStart = onStart; + this.argOnEnd = onEnd; + this.argOnError = onError; + } + + /** + * This is a legacy method only called once for an entire run tree + * therefore not useful here + * @param {Run} _ Not used + */ + persistRun(_: Run): Promise { + return Promise.resolve(); + } + + async onRunCreate(run: Run) { + if (this.rootId) { + return; + } + + this.rootId = run.id; + + if (this.argOnStart) { + if (this.argOnStart.length === 1) { + await this.argOnStart(run); + } else if (this.argOnStart.length === 2) { + await this.argOnStart(run, this.config); + } + } + } + + async onRunUpdate(run: Run) { + if (run.id !== this.rootId) { + return; + } + if (!run.error) { + if (this.argOnEnd) { + if (this.argOnEnd.length === 1) { + await this.argOnEnd(run); + } else if (this.argOnEnd.length === 2) { + await this.argOnEnd(run, this.config); + } + } + } else if (this.argOnError) { + if (this.argOnError.length === 1) { + await this.argOnError(run); + } else if (this.argOnError.length === 2) { + await this.argOnError(run, this.config); + } + } + } +} diff --git a/langchain/src/agents/toolkits/conversational_retrieval/tool.ts b/langchain/src/agents/toolkits/conversational_retrieval/tool.ts index cc62eb3b28a4..36a15e2ef9a1 100644 --- a/langchain/src/agents/toolkits/conversational_retrieval/tool.ts +++ b/langchain/src/agents/toolkits/conversational_retrieval/tool.ts @@ -19,7 +19,7 @@ export function createRetrieverTool( input, runManager?.getChild("retriever") ); - return formatDocumentsAsString(docs, "\n"); + return formatDocumentsAsString(docs); }; const schema = z.object({ input: z diff --git a/langchain/src/memory/vector_store.ts b/langchain/src/memory/vector_store.ts index 8cdbb4e412fb..a0ba15b781d3 100644 --- a/langchain/src/memory/vector_store.ts +++ b/langchain/src/memory/vector_store.ts @@ -89,7 +89,7 @@ export class VectorStoreRetrieverMemory return { [this.memoryKey]: this.returnDocs ? results - : formatDocumentsAsString(results, "\n"), + : formatDocumentsAsString(results), }; } diff --git a/langchain/src/tools/webbrowser.ts b/langchain/src/tools/webbrowser.ts index c765d4c50492..55ec8249d8a1 100644 --- a/langchain/src/tools/webbrowser.ts +++ b/langchain/src/tools/webbrowser.ts @@ -267,7 +267,7 @@ export class WebBrowser extends Tool { undefined, runManager?.getChild("vectorstore") ); - context = formatDocumentsAsString(results, "\n"); + context = formatDocumentsAsString(results); } const input = `Text:${context}\n\nI need ${ diff --git a/langchain/src/util/document.ts b/langchain/src/util/document.ts index 3867af470ef9..a1ee0afc5285 100644 --- a/langchain/src/util/document.ts +++ b/langchain/src/util/document.ts @@ -7,7 +7,5 @@ import { Document } from "../document.js"; * @param documents * @returns A string of the documents page content, separated by newlines. */ -export const formatDocumentsAsString = ( - documents: Document[], - separator = "\n\n" -): string => documents.map((doc) => doc.pageContent).join(separator); +export const formatDocumentsAsString = (documents: Document[]): string => + documents.map((doc) => doc.pageContent).join("\n\n"); From 645c4d0d54af2403846457ad22e2cd07f6afaeee Mon Sep 17 00:00:00 2001 From: Chase McDougall Date: Fri, 1 Dec 2023 23:06:02 -0500 Subject: [PATCH 11/44] langchain[patch]: feat(Gradient LLM Integration): Add fine-tuned adapter inference support (#3471) * Add adapterId option * rename base example * add adapter example * update gradient docs to include adapter stuff * remove run call from gradient llm inference examples --- .../docs/integrations/llms/gradient_ai.mdx | 13 ++++++-- examples/src/llms/gradient_ai-adapter.ts | 15 ++++++++++ examples/src/llms/gradient_ai-base.ts | 15 ++++++++++ examples/src/llms/gradient_ai.ts | 17 ----------- langchain/src/llms/gradient_ai.ts | 30 ++++++++++++++----- 5 files changed, 63 insertions(+), 27 deletions(-) create mode 100644 examples/src/llms/gradient_ai-adapter.ts create mode 100644 examples/src/llms/gradient_ai-base.ts delete mode 100644 examples/src/llms/gradient_ai.ts diff --git a/docs/core_docs/docs/integrations/llms/gradient_ai.mdx b/docs/core_docs/docs/integrations/llms/gradient_ai.mdx index efd8e139699d..769864d7a971 100644 --- a/docs/core_docs/docs/integrations/llms/gradient_ai.mdx +++ b/docs/core_docs/docs/integrations/llms/gradient_ai.mdx @@ -32,6 +32,15 @@ const model = new GradientLLM({ ## Usage import CodeBlock from "@theme/CodeBlock"; -import GradientLLMExample from "@examples/llms/gradient_ai.ts"; +import GradientLLMBaseExample from "@examples/llms/gradient_ai-base.ts"; +import GradientLLMAdapterExample from "@examples/llms/gradient_ai-adapter.ts"; -{GradientLLMExample} +### Using Gradient's Base Models + +{GradientLLMBaseExample} + +### Using your own fine-tuned Adapters + +The use your own custom adapter simply set `adapterId` during setup. + +{GradientLLMAdapterExample} diff --git a/examples/src/llms/gradient_ai-adapter.ts b/examples/src/llms/gradient_ai-adapter.ts new file mode 100644 index 000000000000..8ce2b71fc755 --- /dev/null +++ b/examples/src/llms/gradient_ai-adapter.ts @@ -0,0 +1,15 @@ +import { GradientLLM } from "langchain/llms/gradient_ai"; + +// Note that inferenceParameters are optional +const model = new GradientLLM({ + adapterId: process.env.GRADIENT_ADAPTER_ID, + inferenceParameters: { + maxGeneratedTokenCount: 20, + temperature: 0, + }, +}); +const res = await model.invoke( + "What would be a good company name for a company that makes colorful socks?" +); + +console.log({ res }); diff --git a/examples/src/llms/gradient_ai-base.ts b/examples/src/llms/gradient_ai-base.ts new file mode 100644 index 000000000000..ad09dc273e36 --- /dev/null +++ b/examples/src/llms/gradient_ai-base.ts @@ -0,0 +1,15 @@ +import { GradientLLM } from "langchain/llms/gradient_ai"; + +// Note that inferenceParameters are optional +const model = new GradientLLM({ + modelSlug: "llama2-7b-chat", + inferenceParameters: { + maxGeneratedTokenCount: 20, + temperature: 0, + }, +}); +const res = await model.invoke( + "What would be a good company name for a company that makes colorful socks?" +); + +console.log({ res }); diff --git a/examples/src/llms/gradient_ai.ts b/examples/src/llms/gradient_ai.ts deleted file mode 100644 index 513e73d4eadb..000000000000 --- a/examples/src/llms/gradient_ai.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { GradientLLM } from "langchain/llms/gradient_ai"; - -export const run = async () => { - // Note that modelParameters are optional - const model = new GradientLLM({ - modelSlug: "llama2-7b-chat", - inferenceParameters: { - maxGeneratedTokenCount: 20, - temperature: 0, - }, - }); - const res = await model.invoke( - "What would be a good company name for a company that makes colorful socks?" - ); - - console.log({ res }); -}; diff --git a/langchain/src/llms/gradient_ai.ts b/langchain/src/llms/gradient_ai.ts index ae1edb7d13f9..782bb8cd114e 100644 --- a/langchain/src/llms/gradient_ai.ts +++ b/langchain/src/llms/gradient_ai.ts @@ -25,6 +25,10 @@ export interface GradientLLMParams extends BaseLLMParams { * Gradient AI Model Slug. */ modelSlug?: string; + /** + * Gradient Adapter ID for custom fine tuned models. + */ + adapterId?: string; } /** @@ -45,6 +49,8 @@ export class GradientLLM extends LLM { modelSlug = "llama2-7b-chat"; + adapterId?: string; + gradientAccessKey?: string; workspaceId?: string; @@ -53,12 +59,13 @@ export class GradientLLM extends LLM { // Gradient AI does not export the BaseModel type. Once it does, we can use it here. // eslint-disable-next-line @typescript-eslint/no-explicit-any - baseModel: any; + model: any; constructor(fields: GradientLLMParams) { super(fields); this.modelSlug = fields?.modelSlug ?? this.modelSlug; + this.adapterId = fields?.adapterId; this.gradientAccessKey = fields?.gradientAccessKey ?? getEnvironmentVariable("GRADIENT_ACCESS_TOKEN"); @@ -90,7 +97,7 @@ export class GradientLLM extends LLM { prompt: string, _options: this["ParsedCallOptions"] ): Promise { - await this.setBaseModel(); + await this.setModel(); // GradientLLM does not export the CompleteResponse type. Once it does, we can use it here. interface CompleteResponse { @@ -99,7 +106,7 @@ export class GradientLLM extends LLM { } const response = (await this.caller.call(async () => - this.baseModel.complete({ + this.model.complete({ query: prompt, ...this.inferenceParameters, }) @@ -108,15 +115,22 @@ export class GradientLLM extends LLM { return response.generatedOutput; } - async setBaseModel() { - if (this.baseModel) return; + async setModel() { + if (this.model) return; const gradient = new Gradient({ accessToken: this.gradientAccessKey, workspaceId: this.workspaceId, }); - this.baseModel = await gradient.getBaseModel({ - baseModelSlug: this.modelSlug, - }); + + if (this.adapterId) { + this.model = await gradient.getModelAdapter({ + modelAdapterId: this.adapterId, + }); + } else { + this.model = await gradient.getBaseModel({ + baseModelSlug: this.modelSlug, + }); + } } } From ce84019b4459f8f8c76cf06d895c99ea782a855f Mon Sep 17 00:00:00 2001 From: Chase McDougall Date: Fri, 1 Dec 2023 23:07:21 -0500 Subject: [PATCH 12/44] langchain[minor]: feat(embedding integration): Gradient AI (#3475) * initial gradient embeddings implementation * format * remove modelslug * update package and entrypoint -> yarn build * map texts and change response reading * add example * add `caller.call` wrapper * add docs * remove run form example * Update gradient_ai.mdx --------- Co-authored-by: Brace Sproul Co-authored-by: Jacob Lee --- docs/api_refs/typedoc.json | 1 + .../text_embedding/gradient_ai.mdx | 39 ++++++ examples/src/embeddings/gradient_ai.ts | 7 ++ langchain/.gitignore | 3 + langchain/package.json | 8 ++ langchain/scripts/create-entrypoints.js | 2 + langchain/src/embeddings/gradient_ai.ts | 117 ++++++++++++++++++ langchain/src/load/import_constants.ts | 1 + langchain/src/load/import_type.d.ts | 3 + 9 files changed, 181 insertions(+) create mode 100644 docs/core_docs/docs/integrations/text_embedding/gradient_ai.mdx create mode 100644 examples/src/embeddings/gradient_ai.ts create mode 100644 langchain/src/embeddings/gradient_ai.ts diff --git a/docs/api_refs/typedoc.json b/docs/api_refs/typedoc.json index 7ff44c31fe65..c03de0c396a3 100644 --- a/docs/api_refs/typedoc.json +++ b/docs/api_refs/typedoc.json @@ -67,6 +67,7 @@ "./langchain/src/embeddings/minimax.ts", "./langchain/src/embeddings/voyage.ts", "./langchain/src/embeddings/llama_cpp.ts", + "./langchain/src/embeddings/gradient_ai.ts", "./langchain/src/llms/load.ts", "./langchain/src/llms/base.ts", "./langchain/src/llms/openai.ts", diff --git a/docs/core_docs/docs/integrations/text_embedding/gradient_ai.mdx b/docs/core_docs/docs/integrations/text_embedding/gradient_ai.mdx new file mode 100644 index 000000000000..a8f9cda8daa2 --- /dev/null +++ b/docs/core_docs/docs/integrations/text_embedding/gradient_ai.mdx @@ -0,0 +1,39 @@ +--- +sidebar_class_name: node-only +--- + +# Gradient AI + +The `GradientEmbeddings` class uses the Gradient AI API to generate embeddings for a given text. + +## Setup + +You'll need to install the official Gradient Node SDK as a peer dependency: + +```bash npm2yarn +npm i @gradientai/nodejs-sdk +``` + +You will need to set the following environment variables for using the Gradient AI API. + +``` +export GRADIENT_ACCESS_TOKEN= +export GRADIENT_WORKSPACE_ID= +``` + +Alternatively, these can be set during the GradientAI Class instantiation as `gradientAccessKey` and `workspaceId` respectively. +For example: + +```typescript +const model = new GradientEmbeddings({ + gradientAccessKey: "My secret Access Token" + workspaceId: "My secret workspace id" +}); +``` + +## Usage + +import CodeBlock from "@theme/CodeBlock"; +import GradientEmbeddingsExample from "@examples/embeddings/gradient_ai.ts"; + +{GradientEmbeddingsExample} diff --git a/examples/src/embeddings/gradient_ai.ts b/examples/src/embeddings/gradient_ai.ts new file mode 100644 index 000000000000..a749c2dc494e --- /dev/null +++ b/examples/src/embeddings/gradient_ai.ts @@ -0,0 +1,7 @@ +import { GradientEmbeddings } from "langchain/embeddings/gradient_ai"; + +const model = new GradientEmbeddings(); +const res = await model.embedQuery( + "What would be a good company name a company that makes colorful socks?" +); +console.log({ res }); diff --git a/langchain/.gitignore b/langchain/.gitignore index 886cf43390bd..ca07764e707e 100644 --- a/langchain/.gitignore +++ b/langchain/.gitignore @@ -145,6 +145,9 @@ embeddings/voyage.d.ts embeddings/llama_cpp.cjs embeddings/llama_cpp.js embeddings/llama_cpp.d.ts +embeddings/gradient_ai.cjs +embeddings/gradient_ai.js +embeddings/gradient_ai.d.ts llms/load.cjs llms/load.js llms/load.d.ts diff --git a/langchain/package.json b/langchain/package.json index 7f7e2888ad6f..f6520cf4742b 100644 --- a/langchain/package.json +++ b/langchain/package.json @@ -157,6 +157,9 @@ "embeddings/llama_cpp.cjs", "embeddings/llama_cpp.js", "embeddings/llama_cpp.d.ts", + "embeddings/gradient_ai.cjs", + "embeddings/gradient_ai.js", + "embeddings/gradient_ai.d.ts", "llms/load.cjs", "llms/load.js", "llms/load.d.ts", @@ -1698,6 +1701,11 @@ "import": "./embeddings/llama_cpp.js", "require": "./embeddings/llama_cpp.cjs" }, + "./embeddings/gradient_ai": { + "types": "./embeddings/gradient_ai.d.ts", + "import": "./embeddings/gradient_ai.js", + "require": "./embeddings/gradient_ai.cjs" + }, "./llms/load": { "types": "./llms/load.d.ts", "import": "./llms/load.js", diff --git a/langchain/scripts/create-entrypoints.js b/langchain/scripts/create-entrypoints.js index 446eaac7856b..a8cf7cfaa321 100644 --- a/langchain/scripts/create-entrypoints.js +++ b/langchain/scripts/create-entrypoints.js @@ -63,6 +63,7 @@ const entrypoints = { "embeddings/minimax": "embeddings/minimax", "embeddings/voyage": "embeddings/voyage", "embeddings/llama_cpp": "embeddings/llama_cpp", + "embeddings/gradient_ai": "embeddings/gradient_ai", // llms "llms/load": "llms/load", "llms/base": "llms/base", @@ -364,6 +365,7 @@ const requiresOptionalDependency = [ "embeddings/hf", "embeddings/hf_transformers", "embeddings/llama_cpp", + "embeddings/gradient_ai", "llms/load", "llms/cohere", "llms/googlevertexai", diff --git a/langchain/src/embeddings/gradient_ai.ts b/langchain/src/embeddings/gradient_ai.ts new file mode 100644 index 000000000000..74375ad438b7 --- /dev/null +++ b/langchain/src/embeddings/gradient_ai.ts @@ -0,0 +1,117 @@ +import { Gradient } from "@gradientai/nodejs-sdk"; +import { getEnvironmentVariable } from "../util/env.js"; +import { chunkArray } from "../util/chunk.js"; +import { Embeddings, EmbeddingsParams } from "./base.js"; + +/** + * Interface for GradientEmbeddings parameters. Extends EmbeddingsParams and + * defines additional parameters specific to the GradientEmbeddings class. + */ +export interface GradientEmbeddingsParams extends EmbeddingsParams { + /** + * Gradient AI Access Token. + * Provide Access Token if you do not wish to automatically pull from env. + */ + gradientAccessKey?: string; + /** + * Gradient Workspace Id. + * Provide workspace id if you do not wish to automatically pull from env. + */ + workspaceId?: string; +} + +/** + * Class for generating embeddings using the Gradient AI's API. Extends the + * Embeddings class and implements GradientEmbeddingsParams and + */ +export class GradientEmbeddings + extends Embeddings + implements GradientEmbeddingsParams +{ + gradientAccessKey?: string; + + workspaceId?: string; + + batchSize = 128; + + model: any; + + constructor(fields: GradientEmbeddingsParams) { + super(fields); + + this.gradientAccessKey = + fields?.gradientAccessKey ?? + getEnvironmentVariable("GRADIENT_ACCESS_TOKEN"); + this.workspaceId = + fields?.workspaceId ?? getEnvironmentVariable("GRADIENT_WORKSPACE_ID"); + + if (!this.gradientAccessKey) { + throw new Error("Missing Gradient AI Access Token"); + } + + if (!this.workspaceId) { + throw new Error("Missing Gradient AI Workspace ID"); + } + } + + /** + * Method to generate embeddings for an array of documents. Splits the + * documents into batches and makes requests to the Gradient API to generate + * embeddings. + * @param texts Array of documents to generate embeddings for. + * @returns Promise that resolves to a 2D array of embeddings for each document. + */ + async embedDocuments(texts: string[]): Promise { + await this.setModel(); + + const mappedTexts = texts.map((text) => ({ input: text })); + + const batches = chunkArray(mappedTexts, this.batchSize); + + const batchRequests = batches.map((batch) => + this.caller.call(async () => + this.model.generateEmbeddings({ + inputs: batch, + }) + ) + ); + const batchResponses = await Promise.all(batchRequests); + + const embeddings: number[][] = []; + for (let i = 0; i < batchResponses.length; i += 1) { + const batch = batches[i]; + const { embeddings: batchResponse } = batchResponses[i]; + for (let j = 0; j < batch.length; j += 1) { + embeddings.push(batchResponse[j].embedding); + } + } + return embeddings; + } + + /** + * Method to generate an embedding for a single document. Calls the + * embedDocuments method with the document as the input. + * @param text Document to generate an embedding for. + * @returns Promise that resolves to an embedding for the document. + */ + async embedQuery(text: string): Promise { + const data = await this.embedDocuments([text]); + return data[0]; + } + + /** + * Method to set the model to use for generating embeddings. + * @sets the class' `model` value to that of the retrieved Embeddings Model. + */ + async setModel() { + if (this.model) return; + + const gradient = new Gradient({ + accessToken: this.gradientAccessKey, + workspaceId: this.workspaceId, + }); + this.model = await gradient.getEmbeddingsModel({ + slug: "bge-large", + }); + } +} diff --git a/langchain/src/load/import_constants.ts b/langchain/src/load/import_constants.ts index a7d4a74ee1f8..9bf8f2b4cb46 100644 --- a/langchain/src/load/import_constants.ts +++ b/langchain/src/load/import_constants.ts @@ -24,6 +24,7 @@ export const optionalImportEntrypoints = [ "langchain/embeddings/googlevertexai", "langchain/embeddings/googlepalm", "langchain/embeddings/llama_cpp", + "langchain/embeddings/gradient_ai", "langchain/llms/load", "langchain/llms/cohere", "langchain/llms/hf", diff --git a/langchain/src/load/import_type.d.ts b/langchain/src/load/import_type.d.ts index 00fb99f9d953..052c975320f8 100644 --- a/langchain/src/load/import_type.d.ts +++ b/langchain/src/load/import_type.d.ts @@ -70,6 +70,9 @@ export interface OptionalImportMap { "langchain/embeddings/llama_cpp"?: | typeof import("../embeddings/llama_cpp.js") | Promise; + "langchain/embeddings/gradient_ai"?: + | typeof import("../embeddings/gradient_ai.js") + | Promise; "langchain/llms/load"?: | typeof import("../llms/load.js") | Promise; From ed48b766a3c50aa5b64ae831b8474f8373f9564d Mon Sep 17 00:00:00 2001 From: Jacob Lee Date: Fri, 1 Dec 2023 20:10:11 -0800 Subject: [PATCH 13/44] multi[patch]: Bump core deps (#3495) * Bump core * Bump core dep --- langchain-core/package.json | 2 +- langchain/package.json | 2 +- yarn.lock | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/langchain-core/package.json b/langchain-core/package.json index 33bcbc21692d..389bb947a772 100644 --- a/langchain-core/package.json +++ b/langchain-core/package.json @@ -1,6 +1,6 @@ { "name": "@langchain/core", - "version": "0.0.4", + "version": "0.0.5", "description": "Core LangChain.js abstractions and schemas", "type": "module", "engines": { diff --git a/langchain/package.json b/langchain/package.json index f6520cf4742b..92b3cdfc23ae 100644 --- a/langchain/package.json +++ b/langchain/package.json @@ -1420,7 +1420,7 @@ }, "dependencies": { "@anthropic-ai/sdk": "^0.9.1", - "@langchain/core": "~0.0.4", + "@langchain/core": "~0.0.5", "binary-extensions": "^2.2.0", "expr-eval": "^2.0.2", "flat": "^5.0.2", diff --git a/yarn.lock b/yarn.lock index f61727ea5e07..e634a96ae3a0 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7997,7 +7997,7 @@ __metadata: languageName: unknown linkType: soft -"@langchain/core@workspace:*, @langchain/core@workspace:langchain-core, @langchain/core@~0.0.4": +"@langchain/core@workspace:*, @langchain/core@workspace:langchain-core, @langchain/core@~0.0.5": version: 0.0.0-use.local resolution: "@langchain/core@workspace:langchain-core" dependencies: @@ -22607,7 +22607,7 @@ __metadata: "@gradientai/nodejs-sdk": ^1.2.0 "@huggingface/inference": ^2.6.4 "@jest/globals": ^29.5.0 - "@langchain/core": ~0.0.4 + "@langchain/core": ~0.0.5 "@mozilla/readability": ^0.4.4 "@notionhq/client": ^2.2.10 "@opensearch-project/opensearch": ^2.2.0 From cd7e640e410771617d334e9bea3b414b240e5f9f Mon Sep 17 00:00:00 2001 From: Jacob Lee Date: Fri, 1 Dec 2023 20:27:34 -0800 Subject: [PATCH 14/44] Fix bug (#3496) --- langchain-core/package.json | 2 +- langchain-core/src/language_models/base.ts | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/langchain-core/package.json b/langchain-core/package.json index 389bb947a772..c37782110022 100644 --- a/langchain-core/package.json +++ b/langchain-core/package.json @@ -1,6 +1,6 @@ { "name": "@langchain/core", - "version": "0.0.5", + "version": "0.0.6", "description": "Core LangChain.js abstractions and schemas", "type": "module", "engines": { diff --git a/langchain-core/src/language_models/base.ts b/langchain-core/src/language_models/base.ts index 458697430198..419345f52523 100644 --- a/langchain-core/src/language_models/base.ts +++ b/langchain-core/src/language_models/base.ts @@ -331,7 +331,9 @@ export abstract class BaseLanguageModel< error ); } - } else { + } + + if (this._encoding) { try { numTokens = this._encoding.encode(content).length; } catch (error) { From 791e314c3b04585f35c34fb8064733c364f356d7 Mon Sep 17 00:00:00 2001 From: Jacob Lee Date: Fri, 1 Dec 2023 20:29:23 -0800 Subject: [PATCH 15/44] langchain[patch]: Miscellaneous test fixes (#3497) * Fix bug * Small fixes --- langchain/package.json | 2 +- .../tests/langchain_tracer.int.test.ts | 6 +-- langchain/src/util/tiktoken.ts | 45 +------------------ yarn.lock | 4 +- 4 files changed, 7 insertions(+), 50 deletions(-) diff --git a/langchain/package.json b/langchain/package.json index 92b3cdfc23ae..4fa0f5f67f78 100644 --- a/langchain/package.json +++ b/langchain/package.json @@ -1420,7 +1420,7 @@ }, "dependencies": { "@anthropic-ai/sdk": "^0.9.1", - "@langchain/core": "~0.0.5", + "@langchain/core": "~0.0.6", "binary-extensions": "^2.2.0", "expr-eval": "^2.0.2", "flat": "^5.0.2", diff --git a/langchain/src/callbacks/tests/langchain_tracer.int.test.ts b/langchain/src/callbacks/tests/langchain_tracer.int.test.ts index b27f01c88338..2f651f2d77f6 100644 --- a/langchain/src/callbacks/tests/langchain_tracer.int.test.ts +++ b/langchain/src/callbacks/tests/langchain_tracer.int.test.ts @@ -87,7 +87,7 @@ test("Test traced chain with tags", async () => { test("Test Traced Agent with concurrency", async () => { process.env.LANGCHAIN_TRACING_V2 = "true"; - const model = new OpenAI({ temperature: 0 }); + const model = new ChatOpenAI({ temperature: 0 }); const tools = [ new SerpAPI(process.env.SERPAPI_API_KEY, { location: "Austin,Texas,United States", @@ -98,7 +98,7 @@ test("Test Traced Agent with concurrency", async () => { ]; const executor = await initializeAgentExecutorWithOptions(tools, model, { - agentType: "zero-shot-react-description", + agentType: "openai-functions", verbose: true, }); @@ -130,7 +130,7 @@ test("Test Traced Agent with chat model", async () => { ]; const executor = await initializeAgentExecutorWithOptions(tools, model, { - agentType: "chat-zero-shot-react-description", + agentType: "openai-functions", verbose: true, metadata: { c: "d" }, }); diff --git a/langchain/src/util/tiktoken.ts b/langchain/src/util/tiktoken.ts index a823b5b18637..5876107bcfab 100644 --- a/langchain/src/util/tiktoken.ts +++ b/langchain/src/util/tiktoken.ts @@ -1,44 +1 @@ -import { - Tiktoken, - TiktokenBPE, - TiktokenEncoding, - TiktokenModel, - getEncodingNameForModel, -} from "js-tiktoken/lite"; -import { AsyncCaller } from "./async_caller.js"; - -const cache: Record> = {}; - -const caller = /* #__PURE__ */ new AsyncCaller({}); - -export async function getEncoding( - encoding: TiktokenEncoding, - options?: { - signal?: AbortSignal; - extendedSpecialTokens?: Record; - } -) { - if (!(encoding in cache)) { - cache[encoding] = caller - .fetch(`https://tiktoken.pages.dev/js/${encoding}.json`, { - signal: options?.signal, - }) - .then((res) => res.json()) - .catch((e) => { - delete cache[encoding]; - throw e; - }); - } - - return new Tiktoken(await cache[encoding], options?.extendedSpecialTokens); -} - -export async function encodingForModel( - model: TiktokenModel, - options?: { - signal?: AbortSignal; - extendedSpecialTokens?: Record; - } -) { - return getEncoding(getEncodingNameForModel(model), options); -} +export * from "@langchain/core/utils/tiktoken"; diff --git a/yarn.lock b/yarn.lock index e634a96ae3a0..4a4b0fe8b086 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7997,7 +7997,7 @@ __metadata: languageName: unknown linkType: soft -"@langchain/core@workspace:*, @langchain/core@workspace:langchain-core, @langchain/core@~0.0.5": +"@langchain/core@workspace:*, @langchain/core@workspace:langchain-core, @langchain/core@~0.0.6": version: 0.0.0-use.local resolution: "@langchain/core@workspace:langchain-core" dependencies: @@ -22607,7 +22607,7 @@ __metadata: "@gradientai/nodejs-sdk": ^1.2.0 "@huggingface/inference": ^2.6.4 "@jest/globals": ^29.5.0 - "@langchain/core": ~0.0.5 + "@langchain/core": ~0.0.6 "@mozilla/readability": ^0.4.4 "@notionhq/client": ^2.2.10 "@opensearch-project/opensearch": ^2.2.0 From 23259f700f632a9cd47d42cd4d5a5a8530b70c3e Mon Sep 17 00:00:00 2001 From: jacoblee93 Date: Fri, 1 Dec 2023 20:43:29 -0800 Subject: [PATCH 16/44] Release 0.0.200 --- langchain/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/langchain/package.json b/langchain/package.json index 4fa0f5f67f78..3d5e56386799 100644 --- a/langchain/package.json +++ b/langchain/package.json @@ -1,6 +1,6 @@ { "name": "langchain", - "version": "0.0.199", + "version": "0.0.200", "description": "Typescript bindings for langchain", "type": "module", "engines": { From 7b6db6b98d80e0d2914adaa8a7abe42ae180f053 Mon Sep 17 00:00:00 2001 From: Brace Sproul Date: Sun, 3 Dec 2023 15:34:44 -0800 Subject: [PATCH 17/44] Added extra chat message class to types for history (#3510) * Added extra chat message class to types for history * chore: lint files --- langchain-core/src/chat_history.ts | 30 ------------ langchain-core/src/runnables/history.ts | 7 ++- .../runnables/tests/runnable_history.test.ts | 47 +++++++++++++++++- langchain-core/src/utils/testing/index.ts | 49 +++++++++++++++++++ 4 files changed, 100 insertions(+), 33 deletions(-) diff --git a/langchain-core/src/chat_history.ts b/langchain-core/src/chat_history.ts index 979323008b3b..6841d19e865e 100644 --- a/langchain-core/src/chat_history.ts +++ b/langchain-core/src/chat_history.ts @@ -32,33 +32,3 @@ export abstract class BaseListChatMessageHistory extends Serializable { return this.addMessage(new AIMessage(message)); } } - -export class FakeChatMessageHistory extends BaseChatMessageHistory { - lc_namespace = ["langchain", "core", "message", "fake"]; - - messages: Array = []; - - constructor() { - super(); - } - - async getMessages(): Promise { - return this.messages; - } - - async addMessage(message: BaseMessage): Promise { - this.messages.push(message); - } - - async addUserMessage(message: string): Promise { - this.messages.push(new HumanMessage(message)); - } - - async addAIChatMessage(message: string): Promise { - this.messages.push(new AIMessage(message)); - } - - async clear(): Promise { - this.messages = []; - } -} diff --git a/langchain-core/src/runnables/history.ts b/langchain-core/src/runnables/history.ts index 662a1cbb4de6..90bf3ad8b7d1 100644 --- a/langchain-core/src/runnables/history.ts +++ b/langchain-core/src/runnables/history.ts @@ -1,5 +1,8 @@ import { BaseCallbackConfig } from "../callbacks/manager.js"; -import { BaseChatMessageHistory } from "../chat_history.js"; +import { + BaseChatMessageHistory, + BaseListChatMessageHistory, +} from "../chat_history.js"; import { AIMessage, BaseMessage, @@ -19,7 +22,7 @@ import { RunnablePassthrough } from "./passthrough.js"; type GetSessionHistoryCallable = ( // eslint-disable-next-line @typescript-eslint/no-explicit-any ...args: Array -) => Promise; +) => Promise; export class RunnableWithMessageHistory< RunInput, diff --git a/langchain-core/src/runnables/tests/runnable_history.test.ts b/langchain-core/src/runnables/tests/runnable_history.test.ts index c6e5d1822e63..4f9e688142c5 100644 --- a/langchain-core/src/runnables/tests/runnable_history.test.ts +++ b/langchain-core/src/runnables/tests/runnable_history.test.ts @@ -4,9 +4,14 @@ import { RunnableConfig } from "../config.js"; import { RunnableWithMessageHistory } from "../history.js"; import { BaseChatMessageHistory, - FakeChatMessageHistory, + BaseListChatMessageHistory, } from "../../chat_history.js"; +import { + FakeChatMessageHistory, + FakeListChatMessageHistory, +} from "../../utils/testing/index.js"; +// For `BaseChatMessageHistory` async function getGetSessionHistory(): Promise< (sessionId: string) => Promise > { @@ -24,6 +29,24 @@ async function getGetSessionHistory(): Promise< return getSessionHistory; } +// Extends `BaseListChatMessageHistory` +async function getListSessionHistory(): Promise< + (sessionId: string) => Promise +> { + const chatHistoryStore: { [key: string]: BaseListChatMessageHistory } = {}; + + async function getSessionHistory( + sessionId: string + ): Promise { + if (!(sessionId in chatHistoryStore)) { + chatHistoryStore[sessionId] = new FakeListChatMessageHistory(); + } + return chatHistoryStore[sessionId]; + } + + return getSessionHistory; +} + test("Runnable with message history", async () => { const runnable = new RunnableLambda({ func: (messages: BaseMessage[]) => @@ -45,3 +68,25 @@ test("Runnable with message history", async () => { output = await withHistory.invoke([new HumanMessage("good bye")], config); expect(output).toBe("you said: hello\ngood bye"); }); + +test("Runnable with message history work with chat list memory", async () => { + const runnable = new RunnableLambda({ + func: (messages: BaseMessage[]) => + `you said: ${messages + .filter((m) => m._getType() === "human") + .map((m) => m.content) + .join("\n")}`, + }); + + const getListMessageHistory = await getListSessionHistory(); + const withHistory = new RunnableWithMessageHistory({ + runnable, + config: {}, + getMessageHistory: getListMessageHistory, + }); + const config: RunnableConfig = { configurable: { sessionId: "1" } }; + let output = await withHistory.invoke([new HumanMessage("hello")], config); + expect(output).toBe("you said: hello"); + output = await withHistory.invoke([new HumanMessage("good bye")], config); + expect(output).toBe("you said: hello\ngood bye"); +}); diff --git a/langchain-core/src/utils/testing/index.ts b/langchain-core/src/utils/testing/index.ts index f26fc33246bd..334fe05934ba 100644 --- a/langchain-core/src/utils/testing/index.ts +++ b/langchain-core/src/utils/testing/index.ts @@ -5,6 +5,10 @@ import { BaseCallbackConfig, CallbackManagerForLLMRun, } from "../../callbacks/manager.js"; +import { + BaseChatMessageHistory, + BaseListChatMessageHistory, +} from "../../chat_history.js"; import { Document } from "../../documents/document.js"; import { BaseChatModel, @@ -15,6 +19,7 @@ import { BaseMessage, AIMessage, AIMessageChunk, + HumanMessage, } from "../../messages/index.js"; import { BaseOutputParser } from "../../output_parsers/base.js"; import { @@ -288,3 +293,47 @@ export class FakeListChatModel extends BaseChatModel { } } } + +export class FakeChatMessageHistory extends BaseChatMessageHistory { + lc_namespace = ["langchain_core", "message", "fake"]; + + messages: Array = []; + + constructor() { + super(); + } + + async getMessages(): Promise { + return this.messages; + } + + async addMessage(message: BaseMessage): Promise { + this.messages.push(message); + } + + async addUserMessage(message: string): Promise { + this.messages.push(new HumanMessage(message)); + } + + async addAIChatMessage(message: string): Promise { + this.messages.push(new AIMessage(message)); + } + + async clear(): Promise { + this.messages = []; + } +} + +export class FakeListChatMessageHistory extends BaseListChatMessageHistory { + lc_namespace = ["langchain_core", "message", "fake"]; + + messages: Array = []; + + constructor() { + super(); + } + + public async addMessage(message: BaseMessage): Promise { + this.messages.push(message); + } +} From a862e9e883d91d578152385cbf51e5fe9dc7f741 Mon Sep 17 00:00:00 2001 From: Chase McDougall Date: Sun, 3 Dec 2023 19:33:08 -0500 Subject: [PATCH 18/44] remove run from watsonx ai example (#3503) --- examples/src/llms/watsonx_ai.ts | 30 ++++++++++++++---------------- 1 file changed, 14 insertions(+), 16 deletions(-) diff --git a/examples/src/llms/watsonx_ai.ts b/examples/src/llms/watsonx_ai.ts index f40a50c6a835..0e1e92edbd4e 100644 --- a/examples/src/llms/watsonx_ai.ts +++ b/examples/src/llms/watsonx_ai.ts @@ -1,20 +1,18 @@ import { WatsonxAI } from "langchain/llms/watsonx_ai"; -export const run = async () => { - // Note that modelParameters are optional - const model = new WatsonxAI({ - modelId: "meta-llama/llama-2-70b-chat", - modelParameters: { - max_new_tokens: 100, - min_new_tokens: 0, - stop_sequences: [], - repetition_penalty: 1, - }, - }); +// Note that modelParameters are optional +const model = new WatsonxAI({ + modelId: "meta-llama/llama-2-70b-chat", + modelParameters: { + max_new_tokens: 100, + min_new_tokens: 0, + stop_sequences: [], + repetition_penalty: 1, + }, +}); - const res = await model.invoke( - "What would be a good company name for a company that makes colorful socks?" - ); +const res = await model.invoke( + "What would be a good company name for a company that makes colorful socks?" +); - console.log({ res }); -}; +console.log({ res }); From f62ff21abfdc82550599cd3d618ca17924c86730 Mon Sep 17 00:00:00 2001 From: Brace Sproul Date: Sun, 3 Dec 2023 16:44:52 -0800 Subject: [PATCH 19/44] core[patch]: Export Runnable history (#3514) --- langchain-core/src/runnables/history.ts | 25 +++++++++++++------------ langchain-core/src/runnables/index.ts | 4 ++++ 2 files changed, 17 insertions(+), 12 deletions(-) diff --git a/langchain-core/src/runnables/history.ts b/langchain-core/src/runnables/history.ts index 90bf3ad8b7d1..1ba5a378b4d8 100644 --- a/langchain-core/src/runnables/history.ts +++ b/langchain-core/src/runnables/history.ts @@ -24,6 +24,18 @@ type GetSessionHistoryCallable = ( ...args: Array ) => Promise; +export interface RunnableWithMessageHistoryInputs + extends Omit< + RunnableBindingArgs, + "bound" + > { + runnable: Runnable; + getMessageHistory: GetSessionHistoryCallable; + inputMessagesKey?: string; + outputMessagesKey?: string; + historyMessagesKey?: string; +} + export class RunnableWithMessageHistory< RunInput, RunOutput @@ -38,18 +50,7 @@ export class RunnableWithMessageHistory< getMessageHistory: GetSessionHistoryCallable; - constructor( - fields: Omit< - RunnableBindingArgs, - "bound" - > & { - runnable: Runnable; - getMessageHistory: GetSessionHistoryCallable; - inputMessagesKey?: string; - outputMessagesKey?: string; - historyMessagesKey?: string; - } - ) { + constructor(fields: RunnableWithMessageHistoryInputs) { let historyChain: Runnable = new RunnableLambda({ func: (input, options) => this._enterHistory(input, options ?? {}), }).withConfig({ runName: "loadHistory" }); diff --git a/langchain-core/src/runnables/index.ts b/langchain-core/src/runnables/index.ts index 866fbe393c13..1409fb0d9580 100644 --- a/langchain-core/src/runnables/index.ts +++ b/langchain-core/src/runnables/index.ts @@ -18,3 +18,7 @@ export type { RunnableConfig, getCallbackMangerForConfig } from "./config.js"; export { RunnablePassthrough, RunnableAssign } from "./passthrough.js"; export { type RouterInput, RouterRunnable } from "./router.js"; export { RunnableBranch, type Branch, type BranchLike } from "./branch.js"; +export { + type RunnableWithMessageHistoryInputs, + RunnableWithMessageHistory, +} from "./history.js"; From a1041ab98c7948384b571fe1c74fbb6162549c80 Mon Sep 17 00:00:00 2001 From: Chase McDougall Date: Sun, 3 Dec 2023 19:45:26 -0500 Subject: [PATCH 20/44] initial docs (#3493) --- .github/contributing/INTEGRATIONS.md | 2 +- .../contributing/integrations/EMBEDDINGS.md | 21 +++++++++++++++++++ 2 files changed, 22 insertions(+), 1 deletion(-) create mode 100644 .github/contributing/integrations/EMBEDDINGS.md diff --git a/.github/contributing/INTEGRATIONS.md b/.github/contributing/INTEGRATIONS.md index 69dfcaa3d401..2c97f813b45c 100644 --- a/.github/contributing/INTEGRATIONS.md +++ b/.github/contributing/INTEGRATIONS.md @@ -152,7 +152,7 @@ Below are links to guides with advice and tips for specific types of integration - [Vector stores](https://github.com/langchain-ai/langchainjs/blob/main/.github/contributing/integrations/VECTOR_STORES.md) (e.g. Pinecone) - [Persistent message stores](https://github.com/langchain-ai/langchainjs/blob/main/.github/contributing/integrations/MESSAGE_STORES.md) (used to persistently store and load raw chat histories, e.g. Redis) - [Document loaders](https://github.com/langchain-ai/langchainjs/blob/main/.github/contributing/integrations/DOCUMENT_LOADERS.md) (used to load documents for later storage into vector stores, e.g. Apify) -- Embeddings (TODO) (e.g. Cohere) +- [Embeddings](https://github.com/langchain-ai/langchainjs/blob/main/.github/contributing/integrations/EMBEDDINGS.md) (used to create embeddings of text documents or strings e.g. Cohere) - [Tools](https://github.com/langchain-ai/langchainjs/blob/main/.github/contributing/integrations/TOOLS.md) (used for agents, e.g. the SERP API tool) This is a living document, so please make a pull request if we're missing anything useful! diff --git a/.github/contributing/integrations/EMBEDDINGS.md b/.github/contributing/integrations/EMBEDDINGS.md new file mode 100644 index 000000000000..ba4d87a63b9a --- /dev/null +++ b/.github/contributing/integrations/EMBEDDINGS.md @@ -0,0 +1,21 @@ +# Contributing third-party Text Embeddings + +This page contains some specific guidelines and examples for contributing integrations with third-party Text Embedding providers. + +**Make sure you read the [general guidelines page](https://github.com/langchain-ai/langchainjs/blob/main/.github/contributing/INTEGRATIONS.md) first!** + +## Example PR + +We'll be referencing this PR adding Gradient Embeddings as an example: https://github.com/langchain-ai/langchainjs/pull/3475 + +## General ideas + +The general idea for adding new third-party Text Embeddings is to subclass the `Embeddings` class and implement the `embedDocuments` and `embedQuery` methods. + +The `embedDocuments` method should take a list of documents and return a list of embeddings for each document. The `embedQuery` method should take a query and return an embedding for that query. + +`embedQuery` can typically be implemented by calling `embedDocuments` with a list containing only the query. + +## Wrap Text Embeddings requests in this.caller + +The base Embeddings class contains an instance property called `caller` that will automatically handle retries, errors, timeouts, and more. You should wrap calls to the LLM in `this.caller.call` [as shown here](https://github.com/langchain-ai/langchainjs/blob/f469ec00d945a3f8421b32f4be78bce3f66a74bb/langchain/src/embeddings/gradient_ai.ts#L72) From 3a9d93441918fe389c930a7fa6b2f9fbe6d65522 Mon Sep 17 00:00:00 2001 From: Brace Sproul Date: Sun, 3 Dec 2023 16:46:01 -0800 Subject: [PATCH 21/44] core[fix]: RunnableFunc config types (#3513) * core[fix]: RunnableFunc config types * cr * chore: lint files --- examples/src/chains/map_reduce_lcel.ts | 6 ++++-- examples/src/embeddings/gradient_ai.ts | 2 +- langchain-core/src/runnables/base.ts | 10 ++++++---- 3 files changed, 11 insertions(+), 7 deletions(-) diff --git a/examples/src/chains/map_reduce_lcel.ts b/examples/src/chains/map_reduce_lcel.ts index ff0b9bec3c55..5615a62500a3 100644 --- a/examples/src/chains/map_reduce_lcel.ts +++ b/examples/src/chains/map_reduce_lcel.ts @@ -62,10 +62,12 @@ const collapseChain = RunnableSequence.from([ // Define a function to collapse a list of documents until the total number of tokens is within the limit const collapse = async ( documents: Document[], - config?: BaseCallbackConfig, + options?: { + config?: BaseCallbackConfig; + }, tokenMax = 4000 ) => { - const editableConfig = config; + const editableConfig = options?.config; let docs = documents; let collapseCount = 1; while ((await getNumTokens(docs)) > tokenMax) { diff --git a/examples/src/embeddings/gradient_ai.ts b/examples/src/embeddings/gradient_ai.ts index a749c2dc494e..f4957bf2d527 100644 --- a/examples/src/embeddings/gradient_ai.ts +++ b/examples/src/embeddings/gradient_ai.ts @@ -1,6 +1,6 @@ import { GradientEmbeddings } from "langchain/embeddings/gradient_ai"; -const model = new GradientEmbeddings(); +const model = new GradientEmbeddings({}); const res = await model.embedQuery( "What would be a good company name a company that makes colorful socks?" ); diff --git a/langchain-core/src/runnables/base.ts b/langchain-core/src/runnables/base.ts index 697bc59f095a..bd97724a15a0 100644 --- a/langchain-core/src/runnables/base.ts +++ b/langchain-core/src/runnables/base.ts @@ -23,10 +23,12 @@ import { RootListenersTracer } from "../tracers/root_listener.js"; export type RunnableFunc = ( input: RunInput, - // eslint-disable-next-line @typescript-eslint/no-explicit-any - options?: Record & { - config?: RunnableConfig; - } + options?: + | { config?: RunnableConfig } + // eslint-disable-next-line @typescript-eslint/no-explicit-any + | Record + // eslint-disable-next-line @typescript-eslint/no-explicit-any + | (Record & { config: RunnableConfig }) ) => RunOutput | Promise; // eslint-disable-next-line @typescript-eslint/no-explicit-any From 03957d4635ef9d1c382bf6bb9494a7695283a7a7 Mon Sep 17 00:00:00 2001 From: Jacob Lee Date: Sun, 3 Dec 2023 16:50:20 -0800 Subject: [PATCH 22/44] Bump core version (#3515) --- langchain-core/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/langchain-core/package.json b/langchain-core/package.json index c37782110022..1927a865c10c 100644 --- a/langchain-core/package.json +++ b/langchain-core/package.json @@ -1,6 +1,6 @@ { "name": "@langchain/core", - "version": "0.0.6", + "version": "0.0.7", "description": "Core LangChain.js abstractions and schemas", "type": "module", "engines": { From db5f920106ec8c8337fb49e1502bf05d947a5fae Mon Sep 17 00:00:00 2001 From: Jacob Lee Date: Mon, 4 Dec 2023 11:30:20 -0800 Subject: [PATCH 23/44] Adds npx create-langchain-integration command (#3512) * Adds npx create-langchain-integration command * Format --- .../.eslintrc.cjs | 70 ++++++ .../.prettierignore | 1 + libs/create-langchain-integration/LICENSE.md | 21 ++ .../create-app.ts | 50 +++++ .../helpers/copy.ts | 50 +++++ .../helpers/git.ts | 61 ++++++ .../helpers/is-folder-empty.ts | 62 ++++++ .../helpers/is-url.ts | 8 + .../helpers/is-writeable.ts | 10 + .../helpers/make-dir.ts | 8 + .../helpers/templates.ts | 114 ++++++++++ .../helpers/validate-pkg.ts | 20 ++ libs/create-langchain-integration/index.ts | 170 +++++++++++++++ .../create-langchain-integration/package.json | 31 +++ .../template/.eslintrc.cjs | 66 ++++++ .../template/.gitignore | 3 + .../template/LICENSE | 21 ++ .../template/README.md | 42 ++++ .../template/jest.config.cjs | 19 ++ .../template/jest.env.cjs | 12 ++ .../template/package.json | 77 +++++++ .../template/scripts/check-tree-shaking.js | 80 +++++++ .../template/scripts/create-entrypoints.js | 100 +++++++++ .../template/scripts/identify-secrets.js | 77 +++++++ .../template/scripts/move-cjs-to-dist.js | 38 ++++ .../template/scripts/release-branch.sh | 6 + .../template/src/chat_models.ts | 88 ++++++++ .../template/src/index.ts | 3 + .../template/src/llms.ts | 73 +++++++ .../template/src/tests/chat_models.test.ts | 5 + .../src/tests/integration.int.test.ts | 5 + .../template/src/tests/llms.test.ts | 5 + .../template/src/tests/vectorstores.test.ts | 5 + .../template/src/vectorstores.ts | 80 +++++++ .../template/tsconfig.cjs.json | 8 + .../template/tsconfig.json | 23 ++ .../tsconfig.json | 23 ++ yarn.lock | 202 +++++++++++++++--- 38 files changed, 1712 insertions(+), 25 deletions(-) create mode 100644 libs/create-langchain-integration/.eslintrc.cjs create mode 100644 libs/create-langchain-integration/.prettierignore create mode 100644 libs/create-langchain-integration/LICENSE.md create mode 100644 libs/create-langchain-integration/create-app.ts create mode 100644 libs/create-langchain-integration/helpers/copy.ts create mode 100644 libs/create-langchain-integration/helpers/git.ts create mode 100644 libs/create-langchain-integration/helpers/is-folder-empty.ts create mode 100644 libs/create-langchain-integration/helpers/is-url.ts create mode 100644 libs/create-langchain-integration/helpers/is-writeable.ts create mode 100644 libs/create-langchain-integration/helpers/make-dir.ts create mode 100644 libs/create-langchain-integration/helpers/templates.ts create mode 100644 libs/create-langchain-integration/helpers/validate-pkg.ts create mode 100644 libs/create-langchain-integration/index.ts create mode 100644 libs/create-langchain-integration/package.json create mode 100644 libs/create-langchain-integration/template/.eslintrc.cjs create mode 100644 libs/create-langchain-integration/template/.gitignore create mode 100644 libs/create-langchain-integration/template/LICENSE create mode 100644 libs/create-langchain-integration/template/README.md create mode 100644 libs/create-langchain-integration/template/jest.config.cjs create mode 100644 libs/create-langchain-integration/template/jest.env.cjs create mode 100644 libs/create-langchain-integration/template/package.json create mode 100644 libs/create-langchain-integration/template/scripts/check-tree-shaking.js create mode 100644 libs/create-langchain-integration/template/scripts/create-entrypoints.js create mode 100644 libs/create-langchain-integration/template/scripts/identify-secrets.js create mode 100644 libs/create-langchain-integration/template/scripts/move-cjs-to-dist.js create mode 100644 libs/create-langchain-integration/template/scripts/release-branch.sh create mode 100644 libs/create-langchain-integration/template/src/chat_models.ts create mode 100644 libs/create-langchain-integration/template/src/index.ts create mode 100644 libs/create-langchain-integration/template/src/llms.ts create mode 100644 libs/create-langchain-integration/template/src/tests/chat_models.test.ts create mode 100644 libs/create-langchain-integration/template/src/tests/integration.int.test.ts create mode 100644 libs/create-langchain-integration/template/src/tests/llms.test.ts create mode 100644 libs/create-langchain-integration/template/src/tests/vectorstores.test.ts create mode 100644 libs/create-langchain-integration/template/src/vectorstores.ts create mode 100644 libs/create-langchain-integration/template/tsconfig.cjs.json create mode 100644 libs/create-langchain-integration/template/tsconfig.json create mode 100644 libs/create-langchain-integration/tsconfig.json diff --git a/libs/create-langchain-integration/.eslintrc.cjs b/libs/create-langchain-integration/.eslintrc.cjs new file mode 100644 index 000000000000..bc166270e4e9 --- /dev/null +++ b/libs/create-langchain-integration/.eslintrc.cjs @@ -0,0 +1,70 @@ +module.exports = { + extends: [ + "airbnb-base", + "eslint:recommended", + "prettier", + "plugin:@typescript-eslint/recommended", + ], + parserOptions: { + ecmaVersion: 12, + parser: "@typescript-eslint/parser", + project: "./tsconfig.json", + sourceType: "module", + }, + plugins: ["@typescript-eslint", "no-instanceof", "eslint-plugin-jest"], + ignorePatterns: [ + "src/utils/@cfworker", + "src/utils/fast-json-patch", + "src/utils/js-sha1", + ".eslintrc.cjs", + "scripts", + "node_modules", + "dist", + "dist-cjs", + "*.js", + "*.cjs", + "*.d.ts", + ], + rules: { + "no-process-env": 2, + "no-instanceof/no-instanceof": 2, + "@typescript-eslint/explicit-module-boundary-types": 0, + "@typescript-eslint/no-empty-function": 0, + "@typescript-eslint/no-shadow": 0, + "@typescript-eslint/no-empty-interface": 0, + "@typescript-eslint/no-use-before-define": ["error", "nofunc"], + "@typescript-eslint/no-unused-vars": ["warn", { args: "none" }], + "@typescript-eslint/no-floating-promises": "error", + "@typescript-eslint/no-misused-promises": "error", + camelcase: 0, + "class-methods-use-this": 0, + "import/extensions": [2, "ignorePackages"], + "import/no-extraneous-dependencies": [ + "error", + { devDependencies: ["**/*.test.ts"] }, + ], + "import/no-unresolved": 0, + "import/prefer-default-export": 0, + "keyword-spacing": "error", + "max-classes-per-file": 0, + "max-len": 0, + "no-await-in-loop": 0, + "no-bitwise": 0, + "no-console": 0, + "no-restricted-syntax": 0, + "no-shadow": 0, + "no-continue": 0, + "no-void": 0, + "no-underscore-dangle": 0, + "no-use-before-define": 0, + "no-useless-constructor": 0, + "no-return-await": 0, + "consistent-return": 0, + "no-else-return": 0, + "func-names": 0, + "no-lonely-if": 0, + "prefer-rest-params": 0, + "new-cap": ["error", { properties: false, capIsNew: false }], + "jest/no-focused-tests": "error", + }, +}; diff --git a/libs/create-langchain-integration/.prettierignore b/libs/create-langchain-integration/.prettierignore new file mode 100644 index 000000000000..53c37a16608c --- /dev/null +++ b/libs/create-langchain-integration/.prettierignore @@ -0,0 +1 @@ +dist \ No newline at end of file diff --git a/libs/create-langchain-integration/LICENSE.md b/libs/create-langchain-integration/LICENSE.md new file mode 100644 index 000000000000..ff1c8d1ed328 --- /dev/null +++ b/libs/create-langchain-integration/LICENSE.md @@ -0,0 +1,21 @@ +The MIT License + +Copyright (c) Harrison Chase + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/libs/create-langchain-integration/create-app.ts b/libs/create-langchain-integration/create-app.ts new file mode 100644 index 000000000000..2db3f2162646 --- /dev/null +++ b/libs/create-langchain-integration/create-app.ts @@ -0,0 +1,50 @@ +/* eslint-disable import/no-extraneous-dependencies */ +import path from "path"; +import { green } from "picocolors"; +import { tryGitInit } from "./helpers/git"; +import { isFolderEmpty } from "./helpers/is-folder-empty"; +import { isWriteable } from "./helpers/is-writeable"; +import { makeDir } from "./helpers/make-dir"; + +import { installTemplate } from "./helpers/templates"; + +export type InstallAppArgs = { + appPath: string; +}; + +export async function createApp({ appPath }: InstallAppArgs): Promise { + const root = path.resolve(appPath); + + if (!(await isWriteable(path.dirname(root)))) { + console.error( + "The application path is not writable, please check folder permissions and try again." + ); + console.error( + "It is likely you do not have write permissions for this folder." + ); + process.exit(1); + } + + const appName = path.basename(root); + + await makeDir(root); + if (!isFolderEmpty(root, appName)) { + process.exit(1); + } + + console.log(`Creating a new LangChain integration in ${green(root)}.`); + console.log(); + + await installTemplate({ root, appName }); + + process.chdir(root); + if (tryGitInit(root)) { + console.log("Initialized a git repository."); + console.log(); + } + + console.log(`${green("Success!")} Created ${appName} at ${appPath}`); + console.log(); + console.log(`Run "cd ${appPath} to see your new integration.`); + console.log(); +} diff --git a/libs/create-langchain-integration/helpers/copy.ts b/libs/create-langchain-integration/helpers/copy.ts new file mode 100644 index 000000000000..514868ea36d0 --- /dev/null +++ b/libs/create-langchain-integration/helpers/copy.ts @@ -0,0 +1,50 @@ +/* eslint-disable import/no-extraneous-dependencies */ +import { async as glob } from "fast-glob"; +import fs from "fs"; +import path from "path"; + +interface CopyOption { + cwd?: string; + rename?: (basename: string) => string; + parents?: boolean; +} + +const identity = (x: string) => x; + +export const copy = async ( + src: string | string[], + dest: string, + { cwd, rename = identity, parents = true }: CopyOption = {} +) => { + const source = typeof src === "string" ? [src] : src; + + if (source.length === 0 || !dest) { + throw new TypeError("`src` and `dest` are required"); + } + + const sourceFiles = await glob(source, { + cwd, + dot: true, + absolute: false, + stats: false, + }); + + const destRelativeToCwd = cwd ? path.resolve(cwd, dest) : dest; + + return Promise.all( + sourceFiles.map(async (p) => { + const dirname = path.dirname(p); + const basename = rename(path.basename(p)); + + const from = cwd ? path.resolve(cwd, p) : p; + const to = parents + ? path.join(destRelativeToCwd, dirname, basename) + : path.join(destRelativeToCwd, basename); + + // Ensure the destination directory exists + await fs.promises.mkdir(path.dirname(to), { recursive: true }); + + return fs.promises.copyFile(from, to); + }) + ); +}; diff --git a/libs/create-langchain-integration/helpers/git.ts b/libs/create-langchain-integration/helpers/git.ts new file mode 100644 index 000000000000..416b381402ec --- /dev/null +++ b/libs/create-langchain-integration/helpers/git.ts @@ -0,0 +1,61 @@ +/* eslint-disable import/no-extraneous-dependencies */ +import { execSync } from "child_process"; +import fs from "fs"; +import path from "path"; + +function isInGitRepository(): boolean { + try { + execSync("git rev-parse --is-inside-work-tree", { stdio: "ignore" }); + return true; + } catch (_) {} + return false; +} + +function isInMercurialRepository(): boolean { + try { + execSync("hg --cwd . root", { stdio: "ignore" }); + return true; + } catch (_) {} + return false; +} + +function isDefaultBranchSet(): boolean { + try { + execSync("git config init.defaultBranch", { stdio: "ignore" }); + return true; + } catch (_) {} + return false; +} + +export function tryGitInit(root: string): boolean { + let didInit = false; + try { + execSync("git --version", { stdio: "ignore" }); + if (isInGitRepository() || isInMercurialRepository()) { + return false; + } + + execSync("git init", { stdio: "ignore" }); + didInit = true; + + if (!isDefaultBranchSet()) { + execSync("git checkout -b main", { stdio: "ignore" }); + } + + execSync("git add -A", { stdio: "ignore" }); + execSync( + 'git commit -m "Initial commit from create-langchain-integration"', + { + stdio: "ignore", + } + ); + return true; + } catch (e) { + if (didInit) { + try { + fs.rmSync(path.join(root, ".git"), { recursive: true, force: true }); + } catch (_) {} + } + return false; + } +} diff --git a/libs/create-langchain-integration/helpers/is-folder-empty.ts b/libs/create-langchain-integration/helpers/is-folder-empty.ts new file mode 100644 index 000000000000..390e65d791ca --- /dev/null +++ b/libs/create-langchain-integration/helpers/is-folder-empty.ts @@ -0,0 +1,62 @@ +/* eslint-disable import/no-extraneous-dependencies */ +import fs from "fs"; +import path from "path"; +import { blue, green } from "picocolors"; + +export function isFolderEmpty(root: string, name: string): boolean { + const validFiles = [ + ".DS_Store", + ".git", + ".gitattributes", + ".gitignore", + ".gitlab-ci.yml", + ".hg", + ".hgcheck", + ".hgignore", + ".idea", + ".npmignore", + ".travis.yml", + "LICENSE", + "Thumbs.db", + "docs", + "mkdocs.yml", + "npm-debug.log", + "yarn-debug.log", + "yarn-error.log", + "yarnrc.yml", + ".yarn", + ]; + + const conflicts = fs + .readdirSync(root) + .filter((file) => !validFiles.includes(file)) + // Support IntelliJ IDEA-based editors + .filter((file) => !/\.iml$/.test(file)); + + if (conflicts.length > 0) { + console.log( + `The directory ${green(name)} contains files that could conflict:` + ); + console.log(); + for (const file of conflicts) { + try { + const stats = fs.lstatSync(path.join(root, file)); + if (stats.isDirectory()) { + console.log(` ${blue(file)}/`); + } else { + console.log(` ${file}`); + } + } catch { + console.log(` ${file}`); + } + } + console.log(); + console.log( + "Either try using a new directory name, or remove the files listed above." + ); + console.log(); + return false; + } + + return true; +} diff --git a/libs/create-langchain-integration/helpers/is-url.ts b/libs/create-langchain-integration/helpers/is-url.ts new file mode 100644 index 000000000000..eb87b975252f --- /dev/null +++ b/libs/create-langchain-integration/helpers/is-url.ts @@ -0,0 +1,8 @@ +export function isUrl(url: string): boolean { + try { + new URL(url); + return true; + } catch (error) { + return false; + } +} diff --git a/libs/create-langchain-integration/helpers/is-writeable.ts b/libs/create-langchain-integration/helpers/is-writeable.ts new file mode 100644 index 000000000000..fa29d60558b3 --- /dev/null +++ b/libs/create-langchain-integration/helpers/is-writeable.ts @@ -0,0 +1,10 @@ +import fs from "fs"; + +export async function isWriteable(directory: string): Promise { + try { + await fs.promises.access(directory, (fs.constants || fs).W_OK); + return true; + } catch (err) { + return false; + } +} diff --git a/libs/create-langchain-integration/helpers/make-dir.ts b/libs/create-langchain-integration/helpers/make-dir.ts new file mode 100644 index 000000000000..9289754987e8 --- /dev/null +++ b/libs/create-langchain-integration/helpers/make-dir.ts @@ -0,0 +1,8 @@ +import fs from "fs"; + +export function makeDir( + root: string, + options = { recursive: true } +): Promise { + return fs.promises.mkdir(root, options); +} diff --git a/libs/create-langchain-integration/helpers/templates.ts b/libs/create-langchain-integration/helpers/templates.ts new file mode 100644 index 000000000000..a6924e4025ab --- /dev/null +++ b/libs/create-langchain-integration/helpers/templates.ts @@ -0,0 +1,114 @@ +import path from "path"; +import fs from "fs/promises"; +import os from "os"; + +import { copy } from "./copy"; + +const DEFAULT_ESLINTRC = `module.exports = { + extends: [ + "airbnb-base", + "eslint:recommended", + "prettier", + "plugin:@typescript-eslint/recommended", + ], + parserOptions: { + ecmaVersion: 12, + parser: "@typescript-eslint/parser", + project: "./tsconfig.json", + sourceType: "module", + }, + plugins: ["@typescript-eslint", "no-instanceof"], + ignorePatterns: [ + ".eslintrc.cjs", + "scripts", + "node_modules", + "dist", + "dist-cjs", + "*.js", + "*.cjs", + "*.d.ts", + ], + rules: { + "no-process-env": 2, + "no-instanceof/no-instanceof": 2, + "@typescript-eslint/explicit-module-boundary-types": 0, + "@typescript-eslint/no-empty-function": 0, + "@typescript-eslint/no-shadow": 0, + "@typescript-eslint/no-empty-interface": 0, + "@typescript-eslint/no-use-before-define": ["error", "nofunc"], + "@typescript-eslint/no-unused-vars": ["warn", { args: "none" }], + "@typescript-eslint/no-floating-promises": "error", + "@typescript-eslint/no-misused-promises": "error", + camelcase: 0, + "class-methods-use-this": 0, + "import/extensions": [2, "ignorePackages"], + "import/no-extraneous-dependencies": [ + "error", + { devDependencies: ["**/*.test.ts"] }, + ], + "import/no-unresolved": 0, + "import/prefer-default-export": 0, + "keyword-spacing": "error", + "max-classes-per-file": 0, + "max-len": 0, + "no-await-in-loop": 0, + "no-bitwise": 0, + "no-console": 0, + "no-restricted-syntax": 0, + "no-shadow": 0, + "no-continue": 0, + "no-void": 0, + "no-underscore-dangle": 0, + "no-use-before-define": 0, + "no-useless-constructor": 0, + "no-return-await": 0, + "consistent-return": 0, + "no-else-return": 0, + "func-names": 0, + "no-lonely-if": 0, + "prefer-rest-params": 0, + "new-cap": ["error", { properties: false, capIsNew: false }], + }, +}; +`; + +/** + * Install a internal template to a given `root` directory. + */ +export async function installTemplate({ appName, root }: any) { + /** + * Copy the template files to the target directory. + */ + const templatePath = path.join(__dirname, "..", "template"); + const copySource = ["**"]; + + console.log(`Initializing project...`); + + await copy(copySource, root, { + parents: true, + cwd: templatePath, + }); + + /** + * Update the package.json scripts. + */ + const packageJsonFile = path.join(root, "package.json"); + const packageJson: any = JSON.parse( + await fs.readFile(packageJsonFile, "utf8") + ); + packageJson.name = appName; + + await fs.writeFile( + packageJsonFile, + JSON.stringify(packageJson, null, 2) + os.EOL + ); + + await fs.writeFile( + path.join(root, ".gitignore"), + ["node_modules", "dist", ".yarn"].join("\n") + os.EOL + ); + + await fs.writeFile(path.join(root, ".eslintrc.cjs"), DEFAULT_ESLINTRC); + + console.log("\nDone!\n"); +} diff --git a/libs/create-langchain-integration/helpers/validate-pkg.ts b/libs/create-langchain-integration/helpers/validate-pkg.ts new file mode 100644 index 000000000000..68317653c8e4 --- /dev/null +++ b/libs/create-langchain-integration/helpers/validate-pkg.ts @@ -0,0 +1,20 @@ +// eslint-disable-next-line import/no-extraneous-dependencies +import validateProjectName from "validate-npm-package-name"; + +export function validateNpmName(name: string): { + valid: boolean; + problems?: string[]; +} { + const nameValidation = validateProjectName(name); + if (nameValidation.validForNewPackages) { + return { valid: true }; + } + + return { + valid: false, + problems: [ + ...(nameValidation.errors || []), + ...(nameValidation.warnings || []), + ], + }; +} diff --git a/libs/create-langchain-integration/index.ts b/libs/create-langchain-integration/index.ts new file mode 100644 index 000000000000..155457b7c23e --- /dev/null +++ b/libs/create-langchain-integration/index.ts @@ -0,0 +1,170 @@ +#!/usr/bin/env node +/* eslint-disable import/no-extraneous-dependencies */ +import Commander from "commander"; +import Conf from "conf"; +import fs from "fs"; +import path from "path"; +import { bold, cyan, green, red, yellow } from "picocolors"; +import prompts from "prompts"; +import checkForUpdate from "update-check"; +import { createApp } from "./create-app"; +import { isFolderEmpty } from "./helpers/is-folder-empty"; +import { validateNpmName } from "./helpers/validate-pkg"; +import packageJson from "./package.json"; + +let projectPath: string = ""; + +const handleSigTerm = () => process.exit(0); + +process.on("SIGINT", handleSigTerm); +process.on("SIGTERM", handleSigTerm); + +const onPromptState = (state: any) => { + if (state.aborted) { + // If we don't re-enable the terminal cursor before exiting + // the program, the cursor will remain hidden + process.stdout.write("\x1B[?25h"); + process.stdout.write("\n"); + process.exit(1); + } +}; + +const program = new Commander.Command(packageJson.name) + .version((packageJson as any).version) + .arguments("") + .usage(`${green("")} [options]`) + .action((name) => { + projectPath = name; + }) + .allowUnknownOption() + .parse(process.argv); + +const packageManager = "yarn"; + +async function run(): Promise { + const conf = new Conf({ projectName: "create-langchain-integration" }); + + if (program.resetPreferences) { + conf.clear(); + console.log(`Preferences reset successfully`); + return; + } + + if (typeof projectPath === "string") { + projectPath = projectPath.trim(); + } + + if (!projectPath) { + const res = await prompts({ + onState: onPromptState, + type: "text", + name: "path", + message: "What is your project named?", + initial: "my-langchain-integration", + validate: (name) => { + const validation = validateNpmName(path.basename(path.resolve(name))); + if (validation.valid) { + return true; + } + return "Invalid project name: " + validation.problems![0]; + }, + }); + + if (typeof res.path === "string") { + projectPath = res.path.trim(); + } + } + + if (!projectPath) { + console.log( + "\nPlease specify the project directory:\n" + + ` ${cyan(program.name())} ${green("")}\n` + + "For example:\n" + + ` ${cyan(program.name())} ${green("my-langchain-integration")}\n\n` + + `Run ${cyan(`${program.name()} --help`)} to see all options.` + ); + process.exit(1); + } + + const resolvedProjectPath = path.resolve(projectPath); + const projectName = path.basename(resolvedProjectPath); + + const { valid, problems } = validateNpmName(projectName); + if (!valid) { + console.error( + `Could not create a project called ${red( + `"${projectName}"` + )} because of npm naming restrictions:` + ); + + problems!.forEach((p) => console.error(` ${red(bold("*"))} ${p}`)); + process.exit(1); + } + + /** + * Verify the project dir is empty or doesn't exist + */ + const root = path.resolve(resolvedProjectPath); + const appName = path.basename(root); + const folderExists = fs.existsSync(root); + + if (folderExists && !isFolderEmpty(root, appName)) { + process.exit(1); + } + + const preferences = conf.get("preferences") || {}; + + await createApp({ + appPath: resolvedProjectPath, + }); + conf.set("preferences", preferences); +} + +const update = checkForUpdate(packageJson).catch(() => null); + +async function notifyUpdate(): Promise { + try { + const res = await update; + if (res?.latest) { + const updateMessage = + packageManager === "yarn" + ? "yarn global add create-langchain-integration@latest" + : packageManager === "pnpm" + ? "pnpm add -g create-langchain-integration@latest" + : "npm i -g create-langchain-integration@latest"; + + console.log( + yellow( + bold("A new version of `create-langchain-integration` is available!") + ) + + "\n" + + "You can update by running: " + + cyan(updateMessage) + + "\n" + ); + } + process.exit(); + } catch { + // ignore error + } +} + +run() + .then(notifyUpdate) + .catch(async (reason) => { + console.log(); + console.log("Aborting installation."); + if (reason.command) { + console.log(` ${cyan(reason.command)} has failed.`); + } else { + console.log( + red("Unexpected error. Please report it as a bug:") + "\n", + reason + ); + } + console.log(); + + await notifyUpdate(); + + process.exit(1); + }); diff --git a/libs/create-langchain-integration/package.json b/libs/create-langchain-integration/package.json new file mode 100644 index 000000000000..91622e85427e --- /dev/null +++ b/libs/create-langchain-integration/package.json @@ -0,0 +1,31 @@ +{ + "name": "create-langchain-integration", + "version": "0.0.3", + "repository": { + "type": "git", + "url": "https://github.com/langchain-ai/langchainjs", + "directory": "libs/create-langchain-integration" + }, + "bin": "./dist/index.js", + "scripts": { + "dev": "ncc build ./index.ts -w -o dist/", + "build": "ncc build ./index.ts -o ./dist/ --minify --no-cache --no-source-map-register", + "format": "prettier --write \"./\"", + "format:check": "prettier --check \"./\"" + }, + "devDependencies": { + "@types/prompts": "^2", + "@types/validate-npm-package-name": "3.0.0", + "@vercel/ncc": "^0.34.0", + "commander": "^2.20.0", + "conf": "^10.2.0", + "dotenv": "^16.3.1", + "fast-glob": "^3.3.2", + "picocolors": "^1.0.0", + "prettier": "^2.8.3", + "prompts": "^2.4.2", + "typescript": "<5.2.0", + "update-check": "^1.5.4", + "validate-npm-package-name": "^5.0.0" + } +} diff --git a/libs/create-langchain-integration/template/.eslintrc.cjs b/libs/create-langchain-integration/template/.eslintrc.cjs new file mode 100644 index 000000000000..344f8a9d6cd9 --- /dev/null +++ b/libs/create-langchain-integration/template/.eslintrc.cjs @@ -0,0 +1,66 @@ +module.exports = { + extends: [ + "airbnb-base", + "eslint:recommended", + "prettier", + "plugin:@typescript-eslint/recommended", + ], + parserOptions: { + ecmaVersion: 12, + parser: "@typescript-eslint/parser", + project: "./tsconfig.json", + sourceType: "module", + }, + plugins: ["@typescript-eslint", "no-instanceof"], + ignorePatterns: [ + ".eslintrc.cjs", + "scripts", + "node_modules", + "dist", + "dist-cjs", + "*.js", + "*.cjs", + "*.d.ts", + ], + rules: { + "no-process-env": 2, + "no-instanceof/no-instanceof": 2, + "@typescript-eslint/explicit-module-boundary-types": 0, + "@typescript-eslint/no-empty-function": 0, + "@typescript-eslint/no-shadow": 0, + "@typescript-eslint/no-empty-interface": 0, + "@typescript-eslint/no-use-before-define": ["error", "nofunc"], + "@typescript-eslint/no-unused-vars": ["warn", { args: "none" }], + "@typescript-eslint/no-floating-promises": "error", + "@typescript-eslint/no-misused-promises": "error", + camelcase: 0, + "class-methods-use-this": 0, + "import/extensions": [2, "ignorePackages"], + "import/no-extraneous-dependencies": [ + "error", + { devDependencies: ["**/*.test.ts"] }, + ], + "import/no-unresolved": 0, + "import/prefer-default-export": 0, + "keyword-spacing": "error", + "max-classes-per-file": 0, + "max-len": 0, + "no-await-in-loop": 0, + "no-bitwise": 0, + "no-console": 0, + "no-restricted-syntax": 0, + "no-shadow": 0, + "no-continue": 0, + "no-void": 0, + "no-underscore-dangle": 0, + "no-use-before-define": 0, + "no-useless-constructor": 0, + "no-return-await": 0, + "consistent-return": 0, + "no-else-return": 0, + "func-names": 0, + "no-lonely-if": 0, + "prefer-rest-params": 0, + "new-cap": ["error", { properties: false, capIsNew: false }], + }, +}; diff --git a/libs/create-langchain-integration/template/.gitignore b/libs/create-langchain-integration/template/.gitignore new file mode 100644 index 000000000000..e399d336f991 --- /dev/null +++ b/libs/create-langchain-integration/template/.gitignore @@ -0,0 +1,3 @@ +node_modules +dist +.yarn \ No newline at end of file diff --git a/libs/create-langchain-integration/template/LICENSE b/libs/create-langchain-integration/template/LICENSE new file mode 100644 index 000000000000..8cd8f501eb49 --- /dev/null +++ b/libs/create-langchain-integration/template/LICENSE @@ -0,0 +1,21 @@ +The MIT License + +Copyright (c) 2023 LangChain + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. \ No newline at end of file diff --git a/libs/create-langchain-integration/template/README.md b/libs/create-langchain-integration/template/README.md new file mode 100644 index 000000000000..2df62645edfb --- /dev/null +++ b/libs/create-langchain-integration/template/README.md @@ -0,0 +1,42 @@ +# langchainjs-integration-template + +Template repo for [LangChain.js](https://github.com/langchain-ai/langchainjs) integration packages. + +## Usage + +Source code lives in the `src/` directory. + +By default, there is a single entrypoint under `langchain_integration/src/index.ts` that you should re-export all relevant classes and functions from. +If you want to add more entrypoints, add them in `langchain_integration/scripts/create-entrypoints.js`. + +To build your source code, run the following commands: + +```bash +$ yarn build +``` + +The build process will automatically create build artifacts for both ESM and CJS. + +Note that because of the way compiled files are created and ignored, the `.gitignore` will be dynamically generated by `scripts/create-entrypoints.js` whenever you run `yarn build`. +This means that if you want to add more ignored files, you'll need to add them under `DEFAULT_GITIGNORE_PATHS` in that script. + +After running `yarn build`, publish a new version with: + +```bash +$ npm publish +``` + +Prettier and ESLint are also configured: + +```bash +$ yarn format +$ yarn lint +``` + +As well as Jest for testing. Test files should live within a `tests/` file in the `src/` folder. Unit tests should end in `.test.ts` and integration tests should +end in `.int.test.ts`: + +```bash +$ yarn test +$ yarn test:int +``` diff --git a/libs/create-langchain-integration/template/jest.config.cjs b/libs/create-langchain-integration/template/jest.config.cjs new file mode 100644 index 000000000000..5cc0b1ab72c6 --- /dev/null +++ b/libs/create-langchain-integration/template/jest.config.cjs @@ -0,0 +1,19 @@ +/** @type {import('ts-jest').JestConfigWithTsJest} */ +module.exports = { + preset: "ts-jest/presets/default-esm", + testEnvironment: "./jest.env.cjs", + modulePathIgnorePatterns: ["dist/", "docs/"], + moduleNameMapper: { + "^(\\.{1,2}/.*)\\.js$": "$1", + }, + transform: { + "^.+\\.tsx?$": ["@swc/jest"], + }, + transformIgnorePatterns: [ + "/node_modules/", + "\\.pnp\\.[^\\/]+$", + "./scripts/jest-setup-after-env.js", + ], + setupFiles: ["dotenv/config"], + testTimeout: 20_000, +}; diff --git a/libs/create-langchain-integration/template/jest.env.cjs b/libs/create-langchain-integration/template/jest.env.cjs new file mode 100644 index 000000000000..2ccedccb8672 --- /dev/null +++ b/libs/create-langchain-integration/template/jest.env.cjs @@ -0,0 +1,12 @@ +const { TestEnvironment } = require("jest-environment-node"); + +class AdjustedTestEnvironmentToSupportFloat32Array extends TestEnvironment { + constructor(config, context) { + // Make `instanceof Float32Array` return true in tests + // to avoid https://github.com/xenova/transformers.js/issues/57 and https://github.com/jestjs/jest/issues/2549 + super(config, context); + this.global.Float32Array = Float32Array; + } +} + +module.exports = AdjustedTestEnvironmentToSupportFloat32Array; diff --git a/libs/create-langchain-integration/template/package.json b/libs/create-langchain-integration/template/package.json new file mode 100644 index 000000000000..be75fdabefeb --- /dev/null +++ b/libs/create-langchain-integration/template/package.json @@ -0,0 +1,77 @@ +{ + "name": "langchain-integration", + "version": "0.0.0", + "description": "Sample integration for LangChain.js", + "type": "module", + "engines": { + "node": ">=18" + }, + "main": "./index.js", + "types": "./index.d.ts", + "repository": { + "type": "git", + "url": "git@github.com:langchain-ai/langchainjs.git" + }, + "scripts": { + "build": "yarn clean && yarn build:esm && yarn build:cjs && yarn build:scripts", + "build:esm": "NODE_OPTIONS=--max-old-space-size=4096 tsc --outDir dist/ && rm -rf dist/tests dist/**/tests", + "build:cjs": "NODE_OPTIONS=--max-old-space-size=4096 tsc --outDir dist-cjs/ -p tsconfig.cjs.json && node scripts/move-cjs-to-dist.js && rm -rf dist-cjs", + "build:watch": "node scripts/create-entrypoints.js && tsc --outDir dist/ --watch", + "build:scripts": "node scripts/create-entrypoints.js && node scripts/check-tree-shaking.js", + "lint": "NODE_OPTIONS=--max-old-space-size=4096 eslint src && dpdm --exit-code circular:1 --no-warning --no-tree src/*.ts src/**/*.ts", + "lint:fix": "yarn lint --fix", + "clean": "rm -rf dist/ && NODE_OPTIONS=--max-old-space-size=4096 node scripts/create-entrypoints.js pre", + "prepack": "yarn build", + "release": "release-it --only-version --config .release-it.json", + "test": "NODE_OPTIONS=--experimental-vm-modules jest --testPathIgnorePatterns=\\.int\\.test.ts --testTimeout 30000 --maxWorkers=50%", + "test:watch": "NODE_OPTIONS=--experimental-vm-modules jest --watch --testPathIgnorePatterns=\\.int\\.test.ts", + "test:single": "NODE_OPTIONS=--experimental-vm-modules yarn run jest --config jest.config.cjs --testTimeout 100000", + "test:int": "NODE_OPTIONS=--experimental-vm-modules jest --testPathPattern=\\.int\\.test.ts --testTimeout 100000 --maxWorkers=50%", + "format": "prettier --write \"src\"", + "format:check": "prettier --check \"src\"" + }, + "author": "LangChain", + "license": "MIT", + "dependencies": { + "@langchain/core": "~0.0.6" + }, + "devDependencies": { + "@jest/globals": "^29.5.0", + "@swc/core": "^1.3.90", + "@swc/jest": "^0.2.29", + "@tsconfig/recommended": "^1.0.3", + "@typescript-eslint/eslint-plugin": "^6.12.0", + "@typescript-eslint/parser": "^6.12.0", + "dotenv": "^16.3.1", + "dpdm": "^3.12.0", + "eslint": "^8.33.0", + "eslint-config-airbnb-base": "^15.0.0", + "eslint-config-prettier": "^8.6.0", + "eslint-plugin-import": "^2.27.5", + "eslint-plugin-no-instanceof": "^1.0.1", + "eslint-plugin-prettier": "^4.2.1", + "jest": "^29.5.0", + "jest-environment-node": "^29.6.4", + "prettier": "^2.8.3", + "rollup": "^4.5.2", + "ts-jest": "^29.1.0", + "typescript": "<5.2.0" + }, + "publishConfig": { + "access": "public" + }, + "exports": { + ".": { + "types": "./index.d.ts", + "import": "./index.js", + "require": "./index.cjs" + }, + "./package.json": "./package.json" + }, + "files": [ + "dist/", + "index.cjs", + "index.js", + "index.d.ts" + ] +} diff --git a/libs/create-langchain-integration/template/scripts/check-tree-shaking.js b/libs/create-langchain-integration/template/scripts/check-tree-shaking.js new file mode 100644 index 000000000000..8073e3d5507b --- /dev/null +++ b/libs/create-langchain-integration/template/scripts/check-tree-shaking.js @@ -0,0 +1,80 @@ +import fs from "fs/promises"; +import { rollup } from "rollup"; + +const packageJson = JSON.parse(await fs.readFile("package.json", "utf-8")); + +export function listEntrypoints() { + const exports = packageJson.exports; + const entrypoints = []; + + for (const [key, value] of Object.entries(exports)) { + if (key === "./package.json") { + continue; + } + if (typeof value === "string") { + entrypoints.push(value); + } else if (typeof value === "object" && value.import) { + entrypoints.push(value.import); + } + } + + return entrypoints; +} + +export function listExternals() { + return [ + ...Object.keys(packageJson.dependencies), + ...Object.keys(packageJson.peerDependencies ?? {}), + /node\:/, + /@langchain\/core\//, + ]; +} + +export async function checkTreeShaking() { + const externals = listExternals(); + const entrypoints = listEntrypoints(); + const consoleLog = console.log; + const reportMap = new Map(); + + for (const entrypoint of entrypoints) { + let sideEffects = ""; + + console.log = function (...args) { + const line = args.length ? args.join(" ") : ""; + if (line.trim().startsWith("First side effect in")) { + sideEffects += line + "\n"; + } + }; + + await rollup({ + external: externals, + input: entrypoint, + experimentalLogSideEffects: true, + }); + + reportMap.set(entrypoint, { + log: sideEffects, + hasSideEffects: sideEffects.length > 0, + }); + } + + console.log = consoleLog; + + let failed = false; + for (const [entrypoint, report] of reportMap) { + if (report.hasSideEffects) { + failed = true; + console.log("---------------------------------"); + console.log(`Tree shaking failed for ${entrypoint}`); + console.log(report.log); + } + } + + if (failed) { + process.exit(1); + } else { + console.log("Tree shaking checks passed!"); + } +} + +checkTreeShaking(); diff --git a/libs/create-langchain-integration/template/scripts/create-entrypoints.js b/libs/create-langchain-integration/template/scripts/create-entrypoints.js new file mode 100644 index 000000000000..01a4daeb25ce --- /dev/null +++ b/libs/create-langchain-integration/template/scripts/create-entrypoints.js @@ -0,0 +1,100 @@ +import * as fs from "fs"; +import * as path from "path"; + +// .gitignore +const DEFAULT_GITIGNORE_PATHS = ["node_modules", "dist", ".yarn"]; + +// This lists all the entrypoints for the library. Each key corresponds to an +// importable path, eg. `import { AgentExecutor } from "langchain/agents"`. +// The value is the path to the file in `src/` that exports the entrypoint. +// This is used to generate the `exports` field in package.json. +// Order is not important. +const entrypoints = { + index: "index", +}; + +// Entrypoints in this list require an optional dependency to be installed. +// Therefore they are not tested in the generated test-exports-* packages. +const requiresOptionalDependency = []; + +const updateJsonFile = (relativePath, updateFunction) => { + const contents = fs.readFileSync(relativePath).toString(); + const res = updateFunction(JSON.parse(contents)); + fs.writeFileSync(relativePath, JSON.stringify(res, null, 2) + "\n"); +}; + +const generateFiles = () => { + const files = [...Object.entries(entrypoints), ["index", "index"]].flatMap( + ([key, value]) => { + const nrOfDots = key.split("/").length - 1; + const relativePath = "../".repeat(nrOfDots) || "./"; + const compiledPath = `${relativePath}dist/${value}.js`; + return [ + [ + `${key}.cjs`, + `module.exports = require('${relativePath}dist/${value}.cjs');`, + ], + [`${key}.js`, `export * from '${compiledPath}'`], + [`${key}.d.ts`, `export * from '${compiledPath}'`], + ]; + } + ); + + return Object.fromEntries(files); +}; + +const updateConfig = () => { + const generatedFiles = generateFiles(); + const filenames = Object.keys(generatedFiles); + + // Update package.json `exports` and `files` fields + updateJsonFile("./package.json", (json) => ({ + ...json, + exports: Object.assign( + Object.fromEntries( + [...Object.keys(entrypoints)].map((key) => { + let entryPoint = { + types: `./${key}.d.ts`, + import: `./${key}.js`, + require: `./${key}.cjs`, + }; + + return [key === "index" ? "." : `./${key}`, entryPoint]; + }) + ), + { "./package.json": "./package.json" } + ), + files: ["dist/", ...filenames], + })); + + // Write generated files + Object.entries(generatedFiles).forEach(([filename, content]) => { + fs.mkdirSync(path.dirname(filename), { recursive: true }); + fs.writeFileSync(filename, content); + }); + + // Update .gitignore + fs.writeFileSync( + "./.gitignore", + filenames.join("\n") + "\n" + DEFAULT_GITIGNORE_PATHS.join("\n") + "\n" + ); +}; + +const cleanGenerated = () => { + const filenames = Object.keys(generateFiles()); + filenames.forEach((fname) => { + try { + fs.unlinkSync(fname); + } catch { + // ignore error + } + }); +}; + +const command = process.argv[2]; + +if (command === "pre") { + cleanGenerated(); +} else { + updateConfig(); +} diff --git a/libs/create-langchain-integration/template/scripts/identify-secrets.js b/libs/create-langchain-integration/template/scripts/identify-secrets.js new file mode 100644 index 000000000000..c54bdd97c870 --- /dev/null +++ b/libs/create-langchain-integration/template/scripts/identify-secrets.js @@ -0,0 +1,77 @@ +import ts from "typescript"; +import * as fs from "fs"; + +export function identifySecrets() { + const secrets = new Set(); + + const tsConfig = ts.parseJsonConfigFileContent( + ts.readJsonConfigFile("./tsconfig.json", (p) => + fs.readFileSync(p, "utf-8") + ), + ts.sys, + "./src/" + ); + + for (const fileName of tsConfig.fileNames.filter( + (fn) => !fn.endsWith("test.ts") + )) { + const sourceFile = ts.createSourceFile( + fileName, + fs.readFileSync(fileName, "utf-8"), + tsConfig.options.target, + true + ); + sourceFile.forEachChild((node) => { + switch (node.kind) { + case ts.SyntaxKind.ClassDeclaration: + case ts.SyntaxKind.ClassExpression: { + node.forEachChild((node) => { + // look for get lc_secrets() + switch (node.kind) { + case ts.SyntaxKind.GetAccessor: { + const property = node; + if (property.name.getText() === "lc_secrets") { + // look for return { ... } + property.body.statements.forEach((stmt) => { + if ( + stmt.kind === ts.SyntaxKind.ReturnStatement && + stmt.expression.kind === + ts.SyntaxKind.ObjectLiteralExpression + ) { + // collect secret identifier + stmt.expression.properties.forEach((element) => { + if ( + element.initializer.kind === + ts.SyntaxKind.StringLiteral + ) { + const secret = element.initializer.text; + + if (secret.toUpperCase() !== secret) { + throw new Error( + `Secret identifier must be uppercase: ${secret} at ${fileName}` + ); + } + if (/\s/.test(secret)) { + throw new Error( + `Secret identifier must not contain whitespace: ${secret} at ${fileName}` + ); + } + + secrets.add(secret); + } + }); + } + }); + } + break; + } + } + }); + break; + } + } + }); + } + + return secrets; +} diff --git a/libs/create-langchain-integration/template/scripts/move-cjs-to-dist.js b/libs/create-langchain-integration/template/scripts/move-cjs-to-dist.js new file mode 100644 index 000000000000..1e89ccca88e9 --- /dev/null +++ b/libs/create-langchain-integration/template/scripts/move-cjs-to-dist.js @@ -0,0 +1,38 @@ +import { resolve, dirname, parse, format } from "node:path"; +import { readdir, readFile, writeFile } from "node:fs/promises"; +import { fileURLToPath } from "node:url"; + +function abs(relativePath) { + return resolve(dirname(fileURLToPath(import.meta.url)), relativePath); +} + +async function moveAndRename(source, dest) { + for (const file of await readdir(abs(source), { withFileTypes: true })) { + if (file.isDirectory()) { + await moveAndRename(`${source}/${file.name}`, `${dest}/${file.name}`); + } else if (file.isFile()) { + const parsed = parse(file.name); + + // Ignore anything that's not a .js file + if (parsed.ext !== ".js") { + continue; + } + + // Rewrite any require statements to use .cjs + const content = await readFile(abs(`${source}/${file.name}`), "utf8"); + const rewritten = content.replace(/require\("(\..+?).js"\)/g, (_, p1) => { + return `require("${p1}.cjs")`; + }); + + // Rename the file to .cjs + const renamed = format({ name: parsed.name, ext: ".cjs" }); + + await writeFile(abs(`${dest}/${renamed}`), rewritten, "utf8"); + } + } +} + +moveAndRename("../dist-cjs", "../dist").catch((err) => { + console.error(err); + process.exit(1); +}); diff --git a/libs/create-langchain-integration/template/scripts/release-branch.sh b/libs/create-langchain-integration/template/scripts/release-branch.sh new file mode 100644 index 000000000000..7504238c5561 --- /dev/null +++ b/libs/create-langchain-integration/template/scripts/release-branch.sh @@ -0,0 +1,6 @@ +#!/usr/bin/env bash + +if [[ $(git branch --show-current) == "main" ]]; then + git checkout -B release + git push -u origin release +fi diff --git a/libs/create-langchain-integration/template/src/chat_models.ts b/libs/create-langchain-integration/template/src/chat_models.ts new file mode 100644 index 000000000000..81683647a0bd --- /dev/null +++ b/libs/create-langchain-integration/template/src/chat_models.ts @@ -0,0 +1,88 @@ +import { type BaseMessage } from "@langchain/core/messages"; +import { type BaseLanguageModelCallOptions } from "@langchain/core/language_models/base"; + +import { CallbackManagerForLLMRun } from "@langchain/core/callbacks/manager"; +import { + type BaseChatModelParams, + SimpleChatModel, +} from "@langchain/core/language_models/chat_models"; + +// Uncomment if implementing streaming + +// import { +// ChatGenerationChunk, +// } from "@langchain/core/outputs"; +// import { +// AIMessageChunk, +// } from "@langchain/core/messages"; + +/** + * Input to chat model class. + */ +export interface ChatIntegrationInput extends BaseChatModelParams {} + +/** + * Integration with a chat model. + */ +export class ChatIntegration< + CallOptions extends BaseLanguageModelCallOptions = BaseLanguageModelCallOptions + > + extends SimpleChatModel + implements ChatIntegrationInput +{ + // Used for tracing, replace with the same name as your class + static lc_name() { + return "ChatIntegration"; + } + + lc_serializable = true; + + constructor(fields?: ChatIntegrationInput) { + super(fields ?? {}); + } + + // Replace + _llmType() { + return "chat_integration"; + } + + /** + * For some given input messages and options, return a string output. + */ + _call( + _messages: BaseMessage[], + _options: this["ParsedCallOptions"], + _runManager?: CallbackManagerForLLMRun + ): Promise { + throw new Error("Not implemented."); + } + + /** + * Implement to support streaming. + * Should yield chunks iteratively. + */ + // async *_streamResponseChunks( + // messages: BaseMessage[], + // options: this["ParsedCallOptions"], + // runManager?: CallbackManagerForLLMRun + // ): AsyncGenerator { + // // All models have a built in `this.caller` property for retries + // const stream = await this.caller.call(async () => + // createStreamMethod() + // ); + // for await (const chunk of stream) { + // if (!chunk.done) { + // yield new ChatGenerationChunk({ + // text: chunk.response, + // message: new AIMessageChunk({ content: chunk.response }), + // }); + // await runManager?.handleLLMNewToken(chunk.response ?? ""); + // } + // } + // } + + /** @ignore */ + _combineLLMOutput() { + return []; + } +} diff --git a/libs/create-langchain-integration/template/src/index.ts b/libs/create-langchain-integration/template/src/index.ts new file mode 100644 index 000000000000..564fb4a3c181 --- /dev/null +++ b/libs/create-langchain-integration/template/src/index.ts @@ -0,0 +1,3 @@ +export * from "./chat_models.js"; +export * from "./llms.js"; +export * from "./vectorstores.js"; diff --git a/libs/create-langchain-integration/template/src/llms.ts b/libs/create-langchain-integration/template/src/llms.ts new file mode 100644 index 000000000000..c3ede8d1f29b --- /dev/null +++ b/libs/create-langchain-integration/template/src/llms.ts @@ -0,0 +1,73 @@ +import { CallbackManagerForLLMRun } from "@langchain/core/callbacks/manager"; +import { LLM, type BaseLLMParams } from "@langchain/core/language_models/llms"; +import { type BaseLanguageModelCallOptions } from "@langchain/core/language_models/base"; + +// Uncomment if implementing streaming + +// import { +// GenerationChunk, +// } from "@langchain/core/outputs"; + +/** + * Input to LLM class. + */ +export interface LLMIntegrationInput extends BaseLLMParams {} + +/** + * Integration with an LLM. + */ +export class LLMIntegration + extends LLM + implements LLMIntegrationInput +{ + // Used for tracing, replace with the same name as your class + static lc_name() { + return "LLMIntegration"; + } + + lc_serializable = true; + + constructor(fields: LLMIntegrationInput) { + super(fields); + } + + // Replace + _llmType() { + return "llm_integration"; + } + + /** + * For some given input string and options, return a string output. + */ + async _call( + _prompt: string, + _options: this["ParsedCallOptions"], + _runManager?: CallbackManagerForLLMRun + ): Promise { + throw new Error("Not implemented."); + } + + /** + * Implement to support streaming. + * Should yield chunks iteratively. + */ + // async *_streamResponseChunks( + // prompt: string, + // options: this["ParsedCallOptions"], + // runManager?: CallbackManagerForLLMRun + // ): AsyncGenerator { + // const stream = await this.caller.call(async () => + // createStream() + // ); + // for await (const chunk of stream) { + // yield new GenerationChunk({ + // text: chunk.response, + // generationInfo: { + // ...chunk, + // response: undefined, + // }, + // }); + // await runManager?.handleLLMNewToken(chunk.response ?? ""); + // } + // } +} diff --git a/libs/create-langchain-integration/template/src/tests/chat_models.test.ts b/libs/create-langchain-integration/template/src/tests/chat_models.test.ts new file mode 100644 index 000000000000..5d609f496501 --- /dev/null +++ b/libs/create-langchain-integration/template/src/tests/chat_models.test.ts @@ -0,0 +1,5 @@ +import { test } from "@jest/globals"; + +test("Test chat model", async () => { + // Your test here +}); diff --git a/libs/create-langchain-integration/template/src/tests/integration.int.test.ts b/libs/create-langchain-integration/template/src/tests/integration.int.test.ts new file mode 100644 index 000000000000..7fce4ce53302 --- /dev/null +++ b/libs/create-langchain-integration/template/src/tests/integration.int.test.ts @@ -0,0 +1,5 @@ +import { test } from "@jest/globals"; + +test("Test chat model", async () => { + // Your integration test here +}); diff --git a/libs/create-langchain-integration/template/src/tests/llms.test.ts b/libs/create-langchain-integration/template/src/tests/llms.test.ts new file mode 100644 index 000000000000..3428ecaaf599 --- /dev/null +++ b/libs/create-langchain-integration/template/src/tests/llms.test.ts @@ -0,0 +1,5 @@ +import { test } from "@jest/globals"; + +test("Test LLM", async () => { + // Your test here +}); diff --git a/libs/create-langchain-integration/template/src/tests/vectorstores.test.ts b/libs/create-langchain-integration/template/src/tests/vectorstores.test.ts new file mode 100644 index 000000000000..023cfbd8b77c --- /dev/null +++ b/libs/create-langchain-integration/template/src/tests/vectorstores.test.ts @@ -0,0 +1,5 @@ +import { test } from "@jest/globals"; + +test("Test vectorstore", async () => { + // Your test here +}); diff --git a/libs/create-langchain-integration/template/src/vectorstores.ts b/libs/create-langchain-integration/template/src/vectorstores.ts new file mode 100644 index 000000000000..27c83543801b --- /dev/null +++ b/libs/create-langchain-integration/template/src/vectorstores.ts @@ -0,0 +1,80 @@ +import { Embeddings } from "@langchain/core/embeddings"; +import { VectorStore } from "@langchain/core/vectorstores"; +import { Document } from "@langchain/core/documents"; + +/** + * Database config for your vectorstore. + */ +export interface VectorstoreIntegrationParams {} + +/** + * Class for managing and operating vector search applications with + * Tigris, an open-source Serverless NoSQL Database and Search Platform. + */ +export class VectorstoreIntegration extends VectorStore { + // Replace + _vectorstoreType(): string { + return "vectorstore_integration"; + } + + constructor(embeddings: Embeddings, params: VectorstoreIntegrationParams) { + super(embeddings, params); + this.embeddings = embeddings; + } + + /** + * Method to add an array of documents to the vectorstore. + * + * Useful to override in case your vectorstore doesn't work directly with embeddings. + */ + async addDocuments( + documents: Document[], + options?: { ids?: string[] } | string[] + ): Promise { + const texts = documents.map(({ pageContent }) => pageContent); + await this.addVectors( + await this.embeddings.embedDocuments(texts), + documents, + options + ); + } + + /** + * Method to add raw vectors to the vectorstore. + */ + async addVectors( + _vectors: number[][], + _documents: Document[], + _options?: { ids?: string[] } | string[] + ) { + throw new Error("Not implemented."); + } + + /** + * Method to perform a similarity search over the vectorstore and return + * the k most similar vectors along with their similarity scores. + */ + async similaritySearchVectorWithScore( + _query: number[], + _k: number, + _filter?: object + ): Promise<[Document, number][]> { + throw new Error("Not implemented."); + } + + /** + * Static method to create a new instance of the vectorstore from an + * array of Document instances. + * + * Other common static initializer names are fromExistingIndex, initialize, and fromTexts. + */ + static async fromDocuments( + docs: Document[], + embeddings: Embeddings, + dbConfig: VectorstoreIntegrationParams + ): Promise { + const instance = new this(embeddings, dbConfig); + await instance.addDocuments(docs); + return instance; + } +} diff --git a/libs/create-langchain-integration/template/tsconfig.cjs.json b/libs/create-langchain-integration/template/tsconfig.cjs.json new file mode 100644 index 000000000000..3b7026ea406c --- /dev/null +++ b/libs/create-langchain-integration/template/tsconfig.cjs.json @@ -0,0 +1,8 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "commonjs", + "declaration": false + }, + "exclude": ["node_modules", "dist", "docs", "**/tests"] +} diff --git a/libs/create-langchain-integration/template/tsconfig.json b/libs/create-langchain-integration/template/tsconfig.json new file mode 100644 index 000000000000..bc85d83b6229 --- /dev/null +++ b/libs/create-langchain-integration/template/tsconfig.json @@ -0,0 +1,23 @@ +{ + "extends": "@tsconfig/recommended", + "compilerOptions": { + "outDir": "../dist", + "rootDir": "./src", + "target": "ES2021", + "lib": ["ES2021", "ES2022.Object", "DOM"], + "module": "ES2020", + "moduleResolution": "nodenext", + "esModuleInterop": true, + "declaration": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "useDefineForClassFields": true, + "strictPropertyInitialization": false, + "allowJs": true, + "strict": true + }, + "include": ["src/**/*"], + "exclude": ["node_modules", "dist", "docs"] +} diff --git a/libs/create-langchain-integration/tsconfig.json b/libs/create-langchain-integration/tsconfig.json new file mode 100644 index 000000000000..8994aa4b3798 --- /dev/null +++ b/libs/create-langchain-integration/tsconfig.json @@ -0,0 +1,23 @@ +{ + "extends": "@tsconfig/recommended", + "compilerOptions": { + "outDir": "./dist", + "rootDir": "./", + "target": "ES2021", + "lib": ["ES2021", "ES2022.Object", "DOM"], + "module": "ES2020", + "moduleResolution": "nodenext", + "esModuleInterop": true, + "declaration": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "useDefineForClassFields": true, + "strictPropertyInitialization": false, + "allowJs": true, + "strict": true, + "resolveJsonModule": true + }, + "exclude": ["node_modules", "dist", "template"] +} diff --git a/yarn.lock b/yarn.lock index 4a4b0fe8b086..e044f63324bd 100644 --- a/yarn.lock +++ b/yarn.lock @@ -11727,6 +11727,16 @@ __metadata: languageName: node linkType: hard +"@types/prompts@npm:^2": + version: 2.4.9 + resolution: "@types/prompts@npm:2.4.9" + dependencies: + "@types/node": "*" + kleur: ^3.0.3 + checksum: 69b8372f4c790b45fea16a46ff8d1bcc71b14579481776b67bd6263637118a7ecb1f12e1311506c29fadc81bf618dc64f1a91f903cfd5be67a0455a227b3e462 + languageName: node + linkType: hard + "@types/prop-types@npm:*": version: 15.7.5 resolution: "@types/prop-types@npm:15.7.5" @@ -11979,6 +11989,13 @@ __metadata: languageName: node linkType: hard +"@types/validate-npm-package-name@npm:3.0.0": + version: 3.0.0 + resolution: "@types/validate-npm-package-name@npm:3.0.0" + checksum: 5de86612ecb6a3f87d22a5cf36b0657d8d0b583b1015040abcf9786bbe9b422a034b88ef07be472cad147c21e11e6dcc0e7c9fe425f331b5a24fd3369233dd33 + languageName: node + linkType: hard + "@types/webgl-ext@npm:0.0.30": version: 0.0.30 resolution: "@types/webgl-ext@npm:0.0.30" @@ -12457,6 +12474,17 @@ __metadata: languageName: node linkType: hard +"@vercel/ncc@npm:^0.34.0": + version: 0.34.0 + resolution: "@vercel/ncc@npm:0.34.0" + dependencies: + node-gyp: latest + bin: + ncc: dist/ncc/cli.js + checksum: 3282578e990572759454f7f8d7ef025f2ae060b261fcb28a96d165350e3665438dc8fb72caf122081d6c57277e4899dab30ea0d8759bfd90baf37286cb887597 + languageName: node + linkType: hard + "@vercel/postgres@npm:^0.5.0": version: 0.5.0 resolution: "@vercel/postgres@npm:0.5.0" @@ -12893,7 +12921,7 @@ __metadata: languageName: node linkType: hard -"ajv@npm:^8.0.0, ajv@npm:^8.12.0, ajv@npm:^8.9.0": +"ajv@npm:^8.0.0, ajv@npm:^8.12.0, ajv@npm:^8.6.3, ajv@npm:^8.9.0": version: 8.12.0 resolution: "ajv@npm:8.12.0" dependencies: @@ -13451,6 +13479,13 @@ __metadata: languageName: node linkType: hard +"atomically@npm:^1.7.0": + version: 1.7.0 + resolution: "atomically@npm:1.7.0" + checksum: 991153b17334597f93b58e831bea9851e57ed9cd41d8f33991be063f170b5cc8ec7ff8605f3eb95c1d389c2ad651039e9eb8f2b795e24833c2ceb944f347373a + languageName: node + linkType: hard + "autoprefixer@npm:^10.0.1, autoprefixer@npm:^10.4.12, autoprefixer@npm:^10.4.7": version: 10.4.16 resolution: "autoprefixer@npm:10.4.16" @@ -14161,6 +14196,15 @@ __metadata: languageName: node linkType: hard +"builtins@npm:^5.0.0": + version: 5.0.1 + resolution: "builtins@npm:5.0.1" + dependencies: + semver: ^7.0.0 + checksum: 66d204657fe36522822a95b288943ad11b58f5eaede235b11d8c4edaa28ce4800087d44a2681524c340494aadb120a0068011acabe99d30e8f11a7d826d83515 + languageName: node + linkType: hard + "bundle-name@npm:^3.0.0": version: 3.0.0 resolution: "bundle-name@npm:3.0.0" @@ -15217,6 +15261,24 @@ __metadata: languageName: node linkType: hard +"conf@npm:^10.2.0": + version: 10.2.0 + resolution: "conf@npm:10.2.0" + dependencies: + ajv: ^8.6.3 + ajv-formats: ^2.1.1 + atomically: ^1.7.0 + debounce-fn: ^4.0.0 + dot-prop: ^6.0.1 + env-paths: ^2.2.1 + json-schema-typed: ^7.0.3 + onetime: ^5.1.2 + pkg-up: ^3.1.0 + semver: ^7.3.5 + checksum: 27066f38a25411c1e72e81a5219e2c7ed675cd39d8aa2a2f1797bb2c9255725e92e335d639334177a23d488b22b1290bbe0708e9a005574e5d83d5432df72bd3 + languageName: node + linkType: hard + "config-chain@npm:^1.1.11": version: 1.1.13 resolution: "config-chain@npm:1.1.13" @@ -15514,6 +15576,28 @@ __metadata: languageName: node linkType: hard +"create-langchain-integration@workspace:libs/create-langchain-integration": + version: 0.0.0-use.local + resolution: "create-langchain-integration@workspace:libs/create-langchain-integration" + dependencies: + "@types/prompts": ^2 + "@types/validate-npm-package-name": 3.0.0 + "@vercel/ncc": ^0.34.0 + commander: ^2.20.0 + conf: ^10.2.0 + dotenv: ^16.3.1 + fast-glob: ^3.3.2 + picocolors: ^1.0.0 + prettier: ^2.8.3 + prompts: ^2.4.2 + typescript: <5.2.0 + update-check: ^1.5.4 + validate-npm-package-name: ^5.0.0 + bin: + create-langchain-integration: ./dist/index.js + languageName: unknown + linkType: soft + "crlf-normalize@npm:^1.0.18": version: 1.0.18 resolution: "crlf-normalize@npm:1.0.18" @@ -15872,6 +15956,15 @@ __metadata: languageName: node linkType: hard +"debounce-fn@npm:^4.0.0": + version: 4.0.0 + resolution: "debounce-fn@npm:4.0.0" + dependencies: + mimic-fn: ^3.0.0 + checksum: 7bf8d142b46a88453bbd6eda083f303049b4c8554af5114bdadfc2da56031030664360e81211ae08b708775e6904db7e6d72a421c4ff473344f4521c2c5e4a22 + languageName: node + linkType: hard + "debug@npm:2.6.9, debug@npm:^2.2.0, debug@npm:^2.6.0": version: 2.6.9 resolution: "debug@npm:2.6.9" @@ -16518,6 +16611,13 @@ __metadata: languageName: node linkType: hard +"dotenv@npm:^16.3.1": + version: 16.3.1 + resolution: "dotenv@npm:16.3.1" + checksum: 15d75e7279018f4bafd0ee9706593dd14455ddb71b3bcba9c52574460b7ccaf67d5cf8b2c08a5af1a9da6db36c956a04a1192b101ee102a3e0cf8817bbcf3dfd + languageName: node + linkType: hard + "dpdm@npm:3.12.0": version: 3.12.0 resolution: "dpdm@npm:3.12.0" @@ -16761,7 +16861,7 @@ __metadata: languageName: node linkType: hard -"env-paths@npm:^2.2.0": +"env-paths@npm:^2.2.0, env-paths@npm:^2.2.1": version: 2.2.1 resolution: "env-paths@npm:2.2.1" checksum: 65b5df55a8bab92229ab2b40dad3b387fad24613263d103a97f91c9fe43ceb21965cd3392b1ccb5d77088021e525c4e0481adb309625d0cb94ade1d1fb8dc17e @@ -18313,7 +18413,7 @@ __metadata: languageName: node linkType: hard -"fast-glob@npm:^3.2.12": +"fast-glob@npm:^3.2.12, fast-glob@npm:^3.3.2": version: 3.3.2 resolution: "fast-glob@npm:3.3.2" dependencies: @@ -22353,6 +22453,13 @@ __metadata: languageName: node linkType: hard +"json-schema-typed@npm:^7.0.3": + version: 7.0.3 + resolution: "json-schema-typed@npm:7.0.3" + checksum: e861b19e97e3cc2b29a429147890157827eeda16ab639a0765b935cf3e22aeb6abbba108e23aef442da806bb1f402bdff21da9c5cb30015f8007594565e110b5 + languageName: node + linkType: hard + "json-stable-stringify-without-jsonify@npm:^1.0.1": version: 1.0.1 resolution: "json-stable-stringify-without-jsonify@npm:1.0.1" @@ -24070,6 +24177,13 @@ __metadata: languageName: node linkType: hard +"mimic-fn@npm:^3.0.0": + version: 3.1.0 + resolution: "mimic-fn@npm:3.1.0" + checksum: f7b167f9115b8bbdf2c3ee55dce9149d14be9e54b237259c4bc1d8d0512ea60f25a1b323f814eb1fe8f5a541662804bcfcfff3202ca58df143edb986849d58db + languageName: node + linkType: hard + "mimic-fn@npm:^4.0.0": version: 4.0.0 resolution: "mimic-fn@npm:4.0.0" @@ -27475,7 +27589,7 @@ __metadata: languageName: node linkType: hard -"rc@npm:1.2.8, rc@npm:^1.2.7, rc@npm:^1.2.8": +"rc@npm:1.2.8, rc@npm:^1.0.1, rc@npm:^1.1.6, rc@npm:^1.2.7, rc@npm:^1.2.8": version: 1.2.8 resolution: "rc@npm:1.2.8" dependencies: @@ -27978,6 +28092,16 @@ __metadata: languageName: node linkType: hard +"registry-auth-token@npm:3.3.2": + version: 3.3.2 + resolution: "registry-auth-token@npm:3.3.2" + dependencies: + rc: ^1.1.6 + safe-buffer: ^5.0.1 + checksum: c9d7ae160a738f1fa825556e3669e6c771d2c0239ce37679f7e8646157a97d0a76464738be075002a1f754ef9bfb913b689f4bbfd5296d28f136fbf98c8c2217 + languageName: node + linkType: hard + "registry-auth-token@npm:^4.0.0": version: 4.2.2 resolution: "registry-auth-token@npm:4.2.2" @@ -27996,6 +28120,15 @@ __metadata: languageName: node linkType: hard +"registry-url@npm:3.1.0": + version: 3.1.0 + resolution: "registry-url@npm:3.1.0" + dependencies: + rc: ^1.0.1 + checksum: 6d223da41b04e1824f5faa63905c6f2e43b216589d72794111573f017352b790aef42cd1f826463062f89d804abb2027e3d9665d2a9a0426a11eedd04d470af3 + languageName: node + linkType: hard + "registry-url@npm:^5.0.0": version: 5.1.0 resolution: "registry-url@npm:5.1.0" @@ -28817,25 +28950,25 @@ __metadata: languageName: node linkType: hard -"semver@npm:^7.1.2": - version: 7.5.3 - resolution: "semver@npm:7.5.3" +"semver@npm:^7.0.0, semver@npm:^7.3.2, semver@npm:^7.3.4, semver@npm:^7.3.8, semver@npm:^7.5.4": + version: 7.5.4 + resolution: "semver@npm:7.5.4" dependencies: lru-cache: ^6.0.0 bin: semver: bin/semver.js - checksum: 9d58db16525e9f749ad0a696a1f27deabaa51f66e91d2fa2b0db3de3e9644e8677de3b7d7a03f4c15bc81521e0c3916d7369e0572dbde250d9bedf5194e2a8a7 + checksum: 12d8ad952fa353b0995bf180cdac205a4068b759a140e5d3c608317098b3575ac2f1e09182206bf2eb26120e1c0ed8fb92c48c592f6099680de56bb071423ca3 languageName: node linkType: hard -"semver@npm:^7.3.2, semver@npm:^7.3.4, semver@npm:^7.3.8, semver@npm:^7.5.4": - version: 7.5.4 - resolution: "semver@npm:7.5.4" +"semver@npm:^7.1.2": + version: 7.5.3 + resolution: "semver@npm:7.5.3" dependencies: lru-cache: ^6.0.0 bin: semver: bin/semver.js - checksum: 12d8ad952fa353b0995bf180cdac205a4068b759a140e5d3c608317098b3575ac2f1e09182206bf2eb26120e1c0ed8fb92c48c592f6099680de56bb071423ca3 + checksum: 9d58db16525e9f749ad0a696a1f27deabaa51f66e91d2fa2b0db3de3e9644e8677de3b7d7a03f4c15bc81521e0c3916d7369e0572dbde250d9bedf5194e2a8a7 languageName: node linkType: hard @@ -30915,6 +31048,16 @@ __metadata: languageName: node linkType: hard +"typescript@npm:<5.2.0, typescript@npm:^5.1.6": + version: 5.1.6 + resolution: "typescript@npm:5.1.6" + bin: + tsc: bin/tsc + tsserver: bin/tsserver + checksum: b2f2c35096035fe1f5facd1e38922ccb8558996331405eb00a5111cc948b2e733163cc22fab5db46992aba7dd520fff637f2c1df4996ff0e134e77d3249a7350 + languageName: node + linkType: hard + "typescript@npm:^4.9.4": version: 4.9.5 resolution: "typescript@npm:4.9.5" @@ -30945,13 +31088,13 @@ __metadata: languageName: node linkType: hard -"typescript@npm:^5.1.6": +"typescript@patch:typescript@<5.2.0#~builtin, typescript@patch:typescript@^5.1.6#~builtin": version: 5.1.6 - resolution: "typescript@npm:5.1.6" + resolution: "typescript@patch:typescript@npm%3A5.1.6#~builtin::version=5.1.6&hash=1f5320" bin: tsc: bin/tsc tsserver: bin/tsserver - checksum: b2f2c35096035fe1f5facd1e38922ccb8558996331405eb00a5111cc948b2e733163cc22fab5db46992aba7dd520fff637f2c1df4996ff0e134e77d3249a7350 + checksum: 21e88b0a0c0226f9cb9fd25b9626fb05b4c0f3fddac521844a13e1f30beb8f14e90bd409a9ac43c812c5946d714d6e0dee12d5d02dfc1c562c5aacfa1f49b606 languageName: node linkType: hard @@ -30985,16 +31128,6 @@ __metadata: languageName: node linkType: hard -"typescript@patch:typescript@^5.1.6#~builtin": - version: 5.1.6 - resolution: "typescript@patch:typescript@npm%3A5.1.6#~builtin::version=5.1.6&hash=1f5320" - bin: - tsc: bin/tsc - tsserver: bin/tsserver - checksum: 21e88b0a0c0226f9cb9fd25b9626fb05b4c0f3fddac521844a13e1f30beb8f14e90bd409a9ac43c812c5946d714d6e0dee12d5d02dfc1c562c5aacfa1f49b606 - languageName: node - linkType: hard - "typesense@npm:^1.5.3": version: 1.5.3 resolution: "typesense@npm:1.5.3" @@ -31422,6 +31555,16 @@ __metadata: languageName: node linkType: hard +"update-check@npm:^1.5.4": + version: 1.5.4 + resolution: "update-check@npm:1.5.4" + dependencies: + registry-auth-token: 3.3.2 + registry-url: 3.1.0 + checksum: 2c9f7de6f030364c5ea02a341e5ae2dfe76da6559b32d40dd3b047b3ac0927408cf92d322c51cd8e009688210a85ccbf1eba449762a65a0d1b14f3cdf1ea5c48 + languageName: node + linkType: hard + "update-notifier@npm:6.0.2": version: 6.0.2 resolution: "update-notifier@npm:6.0.2" @@ -31682,6 +31825,15 @@ __metadata: languageName: node linkType: hard +"validate-npm-package-name@npm:^5.0.0": + version: 5.0.0 + resolution: "validate-npm-package-name@npm:5.0.0" + dependencies: + builtins: ^5.0.0 + checksum: 5342a994986199b3c28e53a8452a14b2bb5085727691ea7aa0d284a6606b127c371e0925ae99b3f1ef7cc7d2c9de75f52eb61a3d1cc45e39bca1e3a9444cbb4e + languageName: node + linkType: hard + "value-equal@npm:^1.0.1": version: 1.0.1 resolution: "value-equal@npm:1.0.1" From f289f3d8b42d42988b04476edbb59bdd3fc1283f Mon Sep 17 00:00:00 2001 From: Brace Sproul Date: Mon, 4 Dec 2023 11:53:52 -0800 Subject: [PATCH 24/44] all[chore]: Use turbo repo to build api refs, docs and more (#3511) * api_refs[chore]: use turbo repo to build api refs and other dependencies * update core docs to also use turbo * cr * use turbo in ci * cr * chore: lint files * cr --- .github/workflows/test-exports.yml | 14 +-- docs/api_refs/package.json | 6 +- docs/api_refs/turbo.json | 18 +++ docs/core_docs/package.json | 7 +- docs/core_docs/turbo.json | 13 +++ langchain-core/src/callbacks/manager.ts | 4 +- .../src/language_models/chat_models.ts | 8 +- langchain-core/src/language_models/llms.ts | 13 ++- .../src/messages/tests/base_message.test.ts | 2 +- package.json | 8 +- turbo.json | 56 ++++----- yarn.lock | 110 +++++++++--------- 12 files changed, 146 insertions(+), 113 deletions(-) create mode 100644 docs/api_refs/turbo.json create mode 100644 docs/core_docs/turbo.json diff --git a/.github/workflows/test-exports.yml b/.github/workflows/test-exports.yml index 15b15338107b..4872490161ff 100644 --- a/.github/workflows/test-exports.yml +++ b/.github/workflows/test-exports.yml @@ -34,7 +34,7 @@ jobs: - name: Install dependencies run: yarn install --immutable - name: Build - run: yarn run build:deps && yarn workspace langchain build + run: yarn run build:deps && yarn build --filter=langchain shell: bash env: SKIP_API_DOCS: true @@ -54,7 +54,7 @@ jobs: - name: Install dependencies run: yarn install --immutable - name: Build - run: yarn run build:deps && yarn workspace langchain build + run: yarn run build:deps && yarn build --filter=langchain shell: bash env: SKIP_API_DOCS: true @@ -74,7 +74,7 @@ jobs: - name: Install dependencies run: yarn install --immutable - name: Build - run: yarn run build:deps && yarn workspace langchain build + run: yarn run build:deps && yarn build --filter=langchain shell: bash env: SKIP_API_DOCS: true @@ -94,7 +94,7 @@ jobs: - name: Install dependencies run: yarn install --immutable - name: Build - run: yarn run build:deps && yarn workspace langchain build + run: yarn run build:deps && yarn build --filter=langchain shell: bash env: SKIP_API_DOCS: true @@ -114,7 +114,7 @@ jobs: - name: Install dependencies run: yarn install --immutable - name: Build - run: yarn run build:deps && yarn workspace langchain build + run: yarn run build:deps && yarn build --filter=langchain shell: bash env: SKIP_API_DOCS: true @@ -134,7 +134,7 @@ jobs: - name: Install dependencies run: yarn install --immutable - name: Build - run: yarn run build:deps && yarn workspace langchain build + run: yarn run build:deps && yarn build --filter=langchain shell: bash env: SKIP_API_DOCS: true @@ -154,7 +154,7 @@ jobs: # - name: Install dependencies # run: yarn install --immutable # - name: Build - # run: yarn run build:deps && yarn workspace langchain build + # run: yarn run build:deps && yarn build --filter=langchain # shell: bash # env: # SKIP_API_DOCS: true diff --git a/docs/api_refs/package.json b/docs/api_refs/package.json index 262b3b25ec30..c14730aabf79 100644 --- a/docs/api_refs/package.json +++ b/docs/api_refs/package.json @@ -1,12 +1,13 @@ { - "name": "api_refs", + "name": "@langchain/api_refs", "version": "0.1.0", "private": true, "scripts": { "dev": "next dev -p 3001", "typedoc": "npx typedoc --options typedoc.json", "build:scripts": "node ./scripts/generate-api-refs.js && node ./scripts/update-typedoc-css.js", - "build": "yarn run build:deps && yarn workspace langchain build && yarn build:scripts && next build", + "build": "yarn turbo run build:next", + "build:next": "next build", "start": "yarn build && next start -p 3001", "lint": "next lint" }, @@ -25,6 +26,7 @@ "postcss": "^8", "tailwindcss": "^3.3.0", "ts-morph": "^20.0.0", + "turbo": "latest", "typescript": "^5" } } diff --git a/docs/api_refs/turbo.json b/docs/api_refs/turbo.json new file mode 100644 index 000000000000..111e2a631744 --- /dev/null +++ b/docs/api_refs/turbo.json @@ -0,0 +1,18 @@ +{ + "$schema": "https://turbo.build/schema.json", + "extends": ["//"], + "pipeline": { + "build:scripts": { + "outputs": ["public/**"], + "dependsOn": [ + "@langchain/openai#build", + "@langchain/anthropic#build", + "langchain#build" + ] + }, + "build:next": { + "outputs": [".next/**", ".vercel/**"], + "dependsOn": ["build:scripts"] + } + } +} diff --git a/docs/core_docs/package.json b/docs/core_docs/package.json index ea690f08c985..5b6c9a24684d 100644 --- a/docs/core_docs/package.json +++ b/docs/core_docs/package.json @@ -1,11 +1,13 @@ { - "name": "core_docs", + "name": "@langchain/core_docs", "version": "0.0.0", "private": true, "scripts": { "docusaurus": "docusaurus", "start": "yarn build:typedoc && rimraf ./docs/api && NODE_OPTIONS=--max-old-space-size=7168 docusaurus start", - "build": "yarn build:typedoc && rimraf ./build && NODE_OPTIONS=--max-old-space-size=7168 DOCUSAURUS_SSR_CONCURRENCY=4 docusaurus build", + "rimraf:build": "rimraf ./build", + "build:docusaurus": "NODE_OPTIONS=--max-old-space-size=7168 DOCUSAURUS_SSR_CONCURRENCY=4 docusaurus build", + "build": "yarn turbo run build:docusaurus", "build:typedoc": "cd ../api_refs && yarn build", "swizzle": "docusaurus swizzle", "deploy": "docusaurus deploy", @@ -47,6 +49,7 @@ "prettier": "^2.7.1", "rimraf": "^5.0.1", "swc-loader": "^0.2.3", + "turbo": "latest", "typedoc": "^0.24.4", "typedoc-plugin-markdown": "next" }, diff --git a/docs/core_docs/turbo.json b/docs/core_docs/turbo.json new file mode 100644 index 000000000000..6f1c5b77b96a --- /dev/null +++ b/docs/core_docs/turbo.json @@ -0,0 +1,13 @@ +{ + "$schema": "https://turbo.build/schema.json", + "extends": ["//"], + "pipeline": { + "rimraf:build": { + "dependsOn": ["@langchain/api_refs#build"] + }, + "build:docusaurus": { + "outputs": [".docusaurus/**", "build/**"], + "dependsOn": ["rimraf:build"] + } + } +} diff --git a/langchain-core/src/callbacks/manager.ts b/langchain-core/src/callbacks/manager.ts index baeccb445618..572234de9660 100644 --- a/langchain-core/src/callbacks/manager.ts +++ b/langchain-core/src/callbacks/manager.ts @@ -1,7 +1,7 @@ import { v4 as uuidv4 } from "uuid"; -import { AgentAction, AgentFinish } from "../agents.js"; +import type { AgentAction, AgentFinish } from "../agents.js"; import type { ChainValues } from "../utils/types.js"; -import { LLMResult } from "../outputs.js"; +import type { LLMResult } from "../outputs.js"; import { BaseCallbackHandler, CallbackHandlerMethods, diff --git a/langchain-core/src/language_models/chat_models.ts b/langchain-core/src/language_models/chat_models.ts index 4a426c00b053..ac8d3d96d5e9 100644 --- a/langchain-core/src/language_models/chat_models.ts +++ b/langchain-core/src/language_models/chat_models.ts @@ -2,17 +2,17 @@ import { AIMessage, BaseMessage, BaseMessageChunk, - BaseMessageLike, + type BaseMessageLike, HumanMessage, coerceMessageLikeToMessage, } from "../messages/index.js"; import { BasePromptValue } from "../prompt_values.js"; import { - LLMResult, + type LLMResult, RUN_KEY, - ChatGeneration, + type ChatGeneration, ChatGenerationChunk, - ChatResult, + type ChatResult, } from "../outputs.js"; import { BaseLanguageModel, diff --git a/langchain-core/src/language_models/llms.ts b/langchain-core/src/language_models/llms.ts index c5ccb205cc7d..2bbccea20edc 100644 --- a/langchain-core/src/language_models/llms.ts +++ b/langchain-core/src/language_models/llms.ts @@ -1,11 +1,16 @@ import { AIMessage, BaseMessage, getBufferString } from "../messages/index.js"; import { BasePromptValue } from "../prompt_values.js"; -import { LLMResult, RUN_KEY, Generation, GenerationChunk } from "../outputs.js"; import { - BaseCallbackConfig, + type LLMResult, + RUN_KEY, + type Generation, + GenerationChunk, +} from "../outputs.js"; +import { + type BaseCallbackConfig, CallbackManager, CallbackManagerForLLMRun, - Callbacks, + type Callbacks, } from "../callbacks/manager.js"; import { BaseLanguageModel, @@ -13,7 +18,7 @@ import { type BaseLanguageModelInput, type BaseLanguageModelParams, } from "./base.js"; -import { RunnableConfig } from "../runnables/config.js"; +import type { RunnableConfig } from "../runnables/config.js"; export type SerializedLLM = { _model: string; diff --git a/langchain-core/src/messages/tests/base_message.test.ts b/langchain-core/src/messages/tests/base_message.test.ts index 0e05d2b00bc0..c7b9d2908c41 100644 --- a/langchain-core/src/messages/tests/base_message.test.ts +++ b/langchain-core/src/messages/tests/base_message.test.ts @@ -1,6 +1,6 @@ import { test } from "@jest/globals"; import { ChatPromptTemplate } from "../../prompts/chat.js"; -import { HumanMessage } from "../../messages/index.js"; +import { HumanMessage } from "../index.js"; test("Test ChatPromptTemplate can format OpenAI content image messages", async () => { const message = new HumanMessage({ diff --git a/package.json b/package.json index af0779770ea1..b821d01e3a71 100644 --- a/package.json +++ b/package.json @@ -19,12 +19,12 @@ "packageManager": "yarn@3.4.1", "scripts": { "build": "turbo run build --filter=\"!test-exports-*\" --concurrency 1", - "build:deps": "yarn workspace @langchain/core build && yarn workspace @langchain/anthropic build && yarn workspace @langchain/openai build", + "build:deps": "yarn build --filter=@langchain/openai --filter=@langchain/anthropic --filter=@langchain/core", "format": "turbo run format", "format:check": "turbo run format:check", "lint": "turbo run lint --concurrency 1", "lint:fix": "yarn lint -- --fix", - "test": "yarn test:unit && yarn workspace @langchain/core build && yarn workspace langchain build && yarn test:exports:docker", + "test": "yarn test:unit && yarn build --filter=langchain && yarn test:exports:docker", "test:unit": "turbo run test --filter @langchain/core --filter langchain", "test:int": "yarn run test:int:deps && turbo run test:integration ; yarn run test:int:deps:down", "test:int:deps": "docker compose -f test-int-deps-docker-compose.yml up -d", @@ -34,8 +34,8 @@ "publish:core": "bash langchain/scripts/release-branch.sh && turbo run --filter @langchain/core build lint test --concurrency 1 && yarn run test:exports:docker && yarn workspace @langchain/core run release && echo '🔗 Open https://github.com/langchain-ai/langchainjs/compare/release?expand=1 and merge the release PR'", "example": "yarn workspace examples start", "precommit": "turbo run precommit", - "docs": "yarn workspace core_docs start", - "docs:api_refs": "yarn workspace api_refs start" + "docs": "yarn workspace @langchain/core_docs start", + "docs:api_refs": "yarn workspace @langchain/api_refs start" }, "author": "LangChain", "license": "MIT", diff --git a/turbo.json b/turbo.json index a3910c255d9d..1324193d7ff9 100644 --- a/turbo.json +++ b/turbo.json @@ -1,38 +1,32 @@ { "$schema": "https://turbo.build/schema.json", - "globalDependencies": [ - "**/.env" - ], + "globalDependencies": ["**/.env"], "pipeline": { - "@langchain/core#build": {}, - "libs/langchain-anthropic#build": { - "dependsOn": [ - "@langchain/core#build" + "@langchain/core#build": { + "outputs": [ + "langchain-core/dist/**", + "langchain-core/dist-cjs/**", + "langchain-core/*.js", + "langchain-core/*.cjs", + "langchain-core/*.d.ts" + ], + "inputs": [ + "langchain-core/src/**", + "langchain-core/scripts/**", + "langchain-core/package.json", + "langchain-core/tsconfig.json" ] }, + "libs/langchain-anthropic#build": { + "dependsOn": ["@langchain/core#build"] + }, "libs/langchain-openai#build": { - "dependsOn": [ - "@langchain/core#build" - ] + "dependsOn": ["@langchain/core#build"] }, "build": { - "dependsOn": [ - "@langchain/core#build", - "^build" - ], - "outputs": [ - "dist/**", - "dist-cjs/**", - "*.js", - "*.cjs", - "*.d.ts" - ], - "inputs": [ - "src/**", - "scripts/**", - "package.json", - "tsconfig.json" - ] + "dependsOn": ["@langchain/core#build", "^build"], + "outputs": ["dist/**", "dist-cjs/**", "*.js", "*.cjs", "*.d.ts"], + "inputs": ["src/**", "scripts/**", "package.json", "tsconfig.json"] }, "lint": { "outputs": [] @@ -45,15 +39,11 @@ }, "test": { "outputs": [], - "dependsOn": [ - "^build" - ] + "dependsOn": ["^build"] }, "test:integration": { "outputs": [], - "dependsOn": [ - "^build" - ] + "dependsOn": ["^build"] }, "precommit": {}, "start": { diff --git a/yarn.lock b/yarn.lock index e044f63324bd..ae44b3e678f9 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7997,6 +7997,27 @@ __metadata: languageName: unknown linkType: soft +"@langchain/api_refs@workspace:docs/api_refs": + version: 0.0.0-use.local + resolution: "@langchain/api_refs@workspace:docs/api_refs" + dependencies: + "@types/node": ^20 + "@types/react": ^18 + "@types/react-dom": ^18 + autoprefixer: ^10.0.1 + eslint: ^8 + eslint-config-next: 14.0.1 + next: 14.0.1 + postcss: ^8 + react: ^18 + react-dom: ^18 + tailwindcss: ^3.3.0 + ts-morph: ^20.0.0 + turbo: latest + typescript: ^5 + languageName: unknown + linkType: soft + "@langchain/core@workspace:*, @langchain/core@workspace:langchain-core, @langchain/core@~0.0.6": version: 0.0.0-use.local resolution: "@langchain/core@workspace:langchain-core" @@ -8030,6 +8051,41 @@ __metadata: languageName: unknown linkType: soft +"@langchain/core_docs@workspace:docs/core_docs": + version: 0.0.0-use.local + resolution: "@langchain/core_docs@workspace:docs/core_docs" + dependencies: + "@babel/eslint-parser": ^7.18.2 + "@docusaurus/core": 2.4.1 + "@docusaurus/preset-classic": 2.4.1 + "@docusaurus/remark-plugin-npm2yarn": ^2.4.1 + "@mdx-js/react": ^1.6.22 + "@mendable/search": ^0.0.160 + "@swc/core": ^1.3.62 + clsx: ^1.2.1 + docusaurus-plugin-typedoc: 1.0.0-next.5 + eslint: ^8.19.0 + eslint-config-airbnb: ^19.0.4 + eslint-config-prettier: ^8.5.0 + eslint-plugin-header: ^3.1.1 + eslint-plugin-import: ^2.26.0 + eslint-plugin-jsx-a11y: ^6.6.0 + eslint-plugin-react: ^7.30.1 + eslint-plugin-react-hooks: ^4.6.0 + json-loader: ^0.5.7 + prettier: ^2.7.1 + process: ^0.11.10 + react: ^17.0.2 + react-dom: ^17.0.2 + rimraf: ^5.0.1 + swc-loader: ^0.2.3 + turbo: latest + typedoc: ^0.24.4 + typedoc-plugin-markdown: next + webpack: ^5.75.0 + languageName: unknown + linkType: soft + "@langchain/openai@workspace:libs/langchain-openai": version: 0.0.0-use.local resolution: "@langchain/openai@workspace:libs/langchain-openai" @@ -13099,26 +13155,6 @@ __metadata: languageName: node linkType: hard -"api_refs@workspace:docs/api_refs": - version: 0.0.0-use.local - resolution: "api_refs@workspace:docs/api_refs" - dependencies: - "@types/node": ^20 - "@types/react": ^18 - "@types/react-dom": ^18 - autoprefixer: ^10.0.1 - eslint: ^8 - eslint-config-next: 14.0.1 - next: 14.0.1 - postcss: ^8 - react: ^18 - react-dom: ^18 - tailwindcss: ^3.3.0 - ts-morph: ^20.0.0 - typescript: ^5 - languageName: unknown - linkType: soft - "apify-client@npm:^2.7.1": version: 2.7.1 resolution: "apify-client@npm:2.7.1" @@ -15475,40 +15511,6 @@ __metadata: languageName: node linkType: hard -"core_docs@workspace:docs/core_docs": - version: 0.0.0-use.local - resolution: "core_docs@workspace:docs/core_docs" - dependencies: - "@babel/eslint-parser": ^7.18.2 - "@docusaurus/core": 2.4.1 - "@docusaurus/preset-classic": 2.4.1 - "@docusaurus/remark-plugin-npm2yarn": ^2.4.1 - "@mdx-js/react": ^1.6.22 - "@mendable/search": ^0.0.160 - "@swc/core": ^1.3.62 - clsx: ^1.2.1 - docusaurus-plugin-typedoc: 1.0.0-next.5 - eslint: ^8.19.0 - eslint-config-airbnb: ^19.0.4 - eslint-config-prettier: ^8.5.0 - eslint-plugin-header: ^3.1.1 - eslint-plugin-import: ^2.26.0 - eslint-plugin-jsx-a11y: ^6.6.0 - eslint-plugin-react: ^7.30.1 - eslint-plugin-react-hooks: ^4.6.0 - json-loader: ^0.5.7 - prettier: ^2.7.1 - process: ^0.11.10 - react: ^17.0.2 - react-dom: ^17.0.2 - rimraf: ^5.0.1 - swc-loader: ^0.2.3 - typedoc: ^0.24.4 - typedoc-plugin-markdown: next - webpack: ^5.75.0 - languageName: unknown - linkType: soft - "cosmiconfig@npm:8.0.0": version: 8.0.0 resolution: "cosmiconfig@npm:8.0.0" From 71a70b1c26bef3814cdba2826ee4c0e62f656061 Mon Sep 17 00:00:00 2001 From: Abderrahim Mellouki Date: Mon, 4 Dec 2023 21:31:59 +0100 Subject: [PATCH 25/44] fix: query parameters are not passed correctly to WolframAlpha API (#3502) * fix: query parameters are not passed correctly to WolframAlpha API * Lint + format * Fix test --------- Co-authored-by: jacoblee93 --- .../src/tools/tests/wolframalpha.test.ts | 47 +++++++++++++++++++ langchain/src/tools/wolframalpha.ts | 4 +- 2 files changed, 50 insertions(+), 1 deletion(-) create mode 100644 langchain/src/tools/tests/wolframalpha.test.ts diff --git a/langchain/src/tools/tests/wolframalpha.test.ts b/langchain/src/tools/tests/wolframalpha.test.ts new file mode 100644 index 000000000000..2c2b7f7fd297 --- /dev/null +++ b/langchain/src/tools/tests/wolframalpha.test.ts @@ -0,0 +1,47 @@ +import { jest, afterEach, beforeEach, describe, expect } from "@jest/globals"; +import { WolframAlphaTool } from "../wolframalpha.js"; + +const MOCK_APP_ID = "[MOCK_APP_ID]"; +const QUERY_1 = "What is 2 + 2?"; +const MOCK_ANSWER = "[MOCK_ANSWER]"; + +describe("wolfram alpha test suite", () => { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + let fetchMock: any; + + beforeEach(() => { + fetchMock = jest.spyOn(global, "fetch").mockImplementation( + async () => + ({ + text: () => Promise.resolve(MOCK_ANSWER), + } as Response) + ); + }); + + afterEach(() => { + fetchMock.mockRestore(); + }); + + test("test query parameters passed correctly", async () => { + const wolframAlpha = new WolframAlphaTool({ + appid: MOCK_APP_ID, + }); + await wolframAlpha._call(QUERY_1); + const [url] = fetchMock.mock.calls[0]; + const parsedUrl = new URL(url); + const params = new URLSearchParams(parsedUrl.search); + + expect(fetchMock).toBeCalledTimes(1); + expect(params.get("appid")).toBe(MOCK_APP_ID); + expect(params.get("input")).toBe(QUERY_1); + }); + + test("test answer retrieved", async () => { + const wolframAlpha = new WolframAlphaTool({ + appid: MOCK_APP_ID, + }); + + const answer = await wolframAlpha._call(QUERY_1); + expect(answer).toBe(MOCK_ANSWER); + }); +}); diff --git a/langchain/src/tools/wolframalpha.ts b/langchain/src/tools/wolframalpha.ts index b486d95e1db2..76658428a734 100644 --- a/langchain/src/tools/wolframalpha.ts +++ b/langchain/src/tools/wolframalpha.ts @@ -31,7 +31,9 @@ export class WolframAlphaTool extends Tool { } async _call(query: string): Promise { - const url = `https://www.wolframalpha.com/api/v1/llm-api?appid=${this.appid}&input=${query}`; + const url = `https://www.wolframalpha.com/api/v1/llm-api?appid=${ + this.appid + }&input=${encodeURIComponent(query)}`; const res = await fetch(url); return res.text(); From e0c23e30293b5b016c12a55dff1f09829cfd4de0 Mon Sep 17 00:00:00 2001 From: Jacob Lee Date: Mon, 4 Dec 2023 13:08:19 -0800 Subject: [PATCH 26/44] Fix linter warning (#3528) --- langchain/src/embeddings/gradient_ai.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/langchain/src/embeddings/gradient_ai.ts b/langchain/src/embeddings/gradient_ai.ts index 74375ad438b7..e03b7a364c0f 100644 --- a/langchain/src/embeddings/gradient_ai.ts +++ b/langchain/src/embeddings/gradient_ai.ts @@ -34,6 +34,7 @@ export class GradientEmbeddings batchSize = 128; + // eslint-disable-next-line @typescript-eslint/no-explicit-any model: any; constructor(fields: GradientEmbeddingsParams) { From 72aa0189e1425c561f677dbd3875c43040f39c75 Mon Sep 17 00:00:00 2001 From: Tsukasa OISHI Date: Tue, 5 Dec 2023 07:54:23 +0900 Subject: [PATCH 27/44] core[patch]: Reducing heap area consumption regardless of the number of prompts (#3519) * Remove unused option * Cache the Tiktoken object * Fix format * Bump core version * Upgrade to js-tiktoken@1.0.8 --------- Co-authored-by: jacoblee93 Co-authored-by: Tat Dat Duong --- langchain-core/package.json | 4 ++-- langchain-core/src/utils/tiktoken.ts | 28 +++++++--------------------- yarn.lock | 11 ++++++++++- 3 files changed, 19 insertions(+), 24 deletions(-) diff --git a/langchain-core/package.json b/langchain-core/package.json index 1927a865c10c..a11c036e3a4d 100644 --- a/langchain-core/package.json +++ b/langchain-core/package.json @@ -1,6 +1,6 @@ { "name": "@langchain/core", - "version": "0.0.7", + "version": "0.0.8", "description": "Core LangChain.js abstractions and schemas", "type": "module", "engines": { @@ -37,7 +37,7 @@ "ansi-styles": "^5.0.0", "camelcase": "6", "decamelize": "1.2.0", - "js-tiktoken": "^1.0.7", + "js-tiktoken": "^1.0.8", "langsmith": "^0.0.48", "p-queue": "^6.6.2", "p-retry": "4", diff --git a/langchain-core/src/utils/tiktoken.ts b/langchain-core/src/utils/tiktoken.ts index a823b5b18637..be930b41eb9d 100644 --- a/langchain-core/src/utils/tiktoken.ts +++ b/langchain-core/src/utils/tiktoken.ts @@ -1,44 +1,30 @@ import { Tiktoken, - TiktokenBPE, TiktokenEncoding, TiktokenModel, getEncodingNameForModel, } from "js-tiktoken/lite"; import { AsyncCaller } from "./async_caller.js"; -const cache: Record> = {}; +const cache: Record> = {}; const caller = /* #__PURE__ */ new AsyncCaller({}); -export async function getEncoding( - encoding: TiktokenEncoding, - options?: { - signal?: AbortSignal; - extendedSpecialTokens?: Record; - } -) { +export async function getEncoding(encoding: TiktokenEncoding) { if (!(encoding in cache)) { cache[encoding] = caller - .fetch(`https://tiktoken.pages.dev/js/${encoding}.json`, { - signal: options?.signal, - }) + .fetch(`https://tiktoken.pages.dev/js/${encoding}.json`) .then((res) => res.json()) + .then((data) => new Tiktoken(data)) .catch((e) => { delete cache[encoding]; throw e; }); } - return new Tiktoken(await cache[encoding], options?.extendedSpecialTokens); + return await cache[encoding]; } -export async function encodingForModel( - model: TiktokenModel, - options?: { - signal?: AbortSignal; - extendedSpecialTokens?: Record; - } -) { - return getEncoding(getEncodingNameForModel(model), options); +export async function encodingForModel(model: TiktokenModel) { + return getEncoding(getEncodingNameForModel(model)); } diff --git a/yarn.lock b/yarn.lock index ae44b3e678f9..d1f3163cabf2 100644 --- a/yarn.lock +++ b/yarn.lock @@ -8038,7 +8038,7 @@ __metadata: eslint-plugin-prettier: ^4.2.1 jest: ^29.5.0 jest-environment-node: ^29.6.4 - js-tiktoken: ^1.0.7 + js-tiktoken: ^1.0.8 langsmith: ^0.0.48 p-queue: ^6.6.2 p-retry: 4 @@ -22255,6 +22255,15 @@ __metadata: languageName: node linkType: hard +"js-tiktoken@npm:^1.0.8": + version: 1.0.8 + resolution: "js-tiktoken@npm:1.0.8" + dependencies: + base64-js: ^1.5.1 + checksum: ac6e666f14661b4e744bd60987e35275668863a686413bb35baca2d9a503bc95fcfa907f3c02b8e8abe4a4a70abe622c06cbb72ce3574b3b929d22f9146c3f85 + languageName: node + linkType: hard + "js-tokens@npm:^3.0.0 || ^4.0.0, js-tokens@npm:^4.0.0": version: 4.0.0 resolution: "js-tokens@npm:4.0.0" From 8b9d6c5066b5a766eac2975d94e2cb640867f97d Mon Sep 17 00:00:00 2001 From: Brace Sproul Date: Mon, 4 Dec 2023 15:20:09 -0800 Subject: [PATCH 28/44] cr (#3536) --- docs/api_refs/turbo.json | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/docs/api_refs/turbo.json b/docs/api_refs/turbo.json index 111e2a631744..5d7266b7ed5e 100644 --- a/docs/api_refs/turbo.json +++ b/docs/api_refs/turbo.json @@ -4,11 +4,7 @@ "pipeline": { "build:scripts": { "outputs": ["public/**"], - "dependsOn": [ - "@langchain/openai#build", - "@langchain/anthropic#build", - "langchain#build" - ] + "dependsOn": ["langchain#build"] }, "build:next": { "outputs": [".next/**", ".vercel/**"], From 7f6df0ebe6467d33417565ac5ae313f84380dbde Mon Sep 17 00:00:00 2001 From: Brace Sproul Date: Mon, 4 Dec 2023 16:18:26 -0800 Subject: [PATCH 29/44] core[tests]: Better tests for runnable history (#3537) * core[tests]: Better tests for runnable history * cr --- langchain-core/src/runnables/history.ts | 6 +++- .../runnables/tests/runnable_history.test.ts | 30 +++++++++++++++++++ 2 files changed, 35 insertions(+), 1 deletion(-) diff --git a/langchain-core/src/runnables/history.ts b/langchain-core/src/runnables/history.ts index 1ba5a378b4d8..fbf12f89c551 100644 --- a/langchain-core/src/runnables/history.ts +++ b/langchain-core/src/runnables/history.ts @@ -27,13 +27,14 @@ type GetSessionHistoryCallable = ( export interface RunnableWithMessageHistoryInputs extends Omit< RunnableBindingArgs, - "bound" + "bound" | "config" > { runnable: Runnable; getMessageHistory: GetSessionHistoryCallable; inputMessagesKey?: string; outputMessagesKey?: string; historyMessagesKey?: string; + config?: RunnableConfig; } export class RunnableWithMessageHistory< @@ -70,8 +71,11 @@ export class RunnableWithMessageHistory< ) .withConfig({ runName: "RunnableWithMessageHistory" }); + const config = fields.config ?? {}; + super({ ...fields, + config, bound, }); this.runnable = fields.runnable; diff --git a/langchain-core/src/runnables/tests/runnable_history.test.ts b/langchain-core/src/runnables/tests/runnable_history.test.ts index 4f9e688142c5..3d9628478bfd 100644 --- a/langchain-core/src/runnables/tests/runnable_history.test.ts +++ b/langchain-core/src/runnables/tests/runnable_history.test.ts @@ -8,8 +8,10 @@ import { } from "../../chat_history.js"; import { FakeChatMessageHistory, + FakeLLM, FakeListChatMessageHistory, } from "../../utils/testing/index.js"; +import { ChatPromptTemplate, MessagesPlaceholder } from "../../prompts/chat.js"; // For `BaseChatMessageHistory` async function getGetSessionHistory(): Promise< @@ -90,3 +92,31 @@ test("Runnable with message history work with chat list memory", async () => { output = await withHistory.invoke([new HumanMessage("good bye")], config); expect(output).toBe("you said: hello\ngood bye"); }); + +test("Runnable with message history and RunnableSequence", async () => { + const prompt = ChatPromptTemplate.fromMessages([ + ["ai", "You are a helpful assistant"], + new MessagesPlaceholder("history"), + ["human", "{input}"], + ]); + const model = new FakeLLM({}); + const chain = prompt.pipe(model); + + const getListMessageHistory = await getListSessionHistory(); + const withHistory = new RunnableWithMessageHistory({ + runnable: chain, + config: {}, + getMessageHistory: getListMessageHistory, + inputMessagesKey: "input", + historyMessagesKey: "history", + }); + const config: RunnableConfig = { configurable: { sessionId: "1" } }; + let output = await withHistory.invoke({ input: "hello" }, config); + expect(output).toBe("AI: You are a helpful assistant\nHuman: hello"); + output = await withHistory.invoke({ input: "good bye" }, config); + expect(output).toBe(`AI: You are a helpful assistant +Human: hello +AI: AI: You are a helpful assistant +Human: hello +Human: good bye`); +}); From d1b8a586d31c95ec38d5cbd7e62f8c27584e47ec Mon Sep 17 00:00:00 2001 From: Erick Friis Date: Mon, 4 Dec 2023 16:57:42 -0800 Subject: [PATCH 30/44] docs[patch]: search experiment (#3538) * docs[patch]: search experiment * lockfile change --- .../docs/_static/js/mendablesearch.js | 56 -- docs/core_docs/docusaurus.config.js | 15 +- docs/core_docs/package.json | 7 +- docs/core_docs/src/css/custom.css | 19 - docs/core_docs/src/theme/SearchBar.js | 35 -- yarn.lock | 499 +++++++----------- 6 files changed, 210 insertions(+), 421 deletions(-) delete mode 100644 docs/core_docs/docs/_static/js/mendablesearch.js delete mode 100644 docs/core_docs/src/theme/SearchBar.js diff --git a/docs/core_docs/docs/_static/js/mendablesearch.js b/docs/core_docs/docs/_static/js/mendablesearch.js deleted file mode 100644 index 909d08b8df06..000000000000 --- a/docs/core_docs/docs/_static/js/mendablesearch.js +++ /dev/null @@ -1,56 +0,0 @@ -document.addEventListener('DOMContentLoaded', () => { - // Load the external dependencies - function loadScript(src, onLoadCallback) { - const script = document.createElement('script'); - script.src = src; - script.onload = onLoadCallback; - document.head.appendChild(script); - } - - function createRootElement() { - const rootElement = document.createElement('div'); - rootElement.id = 'my-component-root'; - document.body.appendChild(rootElement); - return rootElement; - } - - - - function initializeMendable() { - const rootElement = createRootElement(); - const { MendableFloatingButton } = Mendable; - - - const iconSpan1 = React.createElement('span', { - }, '🦜'); - - const iconSpan2 = React.createElement('span', { - }, '🔗'); - - const icon = React.createElement('p', { - style: { color: '#ffffff', fontSize: '22px',width: '48px', height: '48px', margin: '0px', padding: '0px', display: 'flex', alignItems: 'center', justifyContent: 'center', textAlign: 'center' }, - }, [iconSpan1, iconSpan2]); - - const mendableFloatingButton = React.createElement( - MendableFloatingButton, - { - style: { darkMode: false, accentColor: '#010810' }, - floatingButtonStyle: { color: '#ffffff', backgroundColor: '#010810' }, - anon_key: '82842b36-3ea6-49b2-9fb8-52cfc4bde6bf', // Mendable Search Public ANON key, ok to be public - messageSettings: { - openSourcesInNewTab: false, - prettySources: true // Prettify the sources displayed now - }, - icon: icon, - } - ); - - ReactDOM.render(mendableFloatingButton, rootElement); - } - - loadScript('https://unpkg.com/react@17/umd/react.production.min.js', () => { - loadScript('https://unpkg.com/react-dom@17/umd/react-dom.production.min.js', () => { - loadScript('https://unpkg.com/@mendable/search@0.0.102/dist/umd/mendable.min.js', initializeMendable); - }); - }); -}); \ No newline at end of file diff --git a/docs/core_docs/docusaurus.config.js b/docs/core_docs/docusaurus.config.js index cfc158dda39c..f5938edae362 100644 --- a/docs/core_docs/docusaurus.config.js +++ b/docs/core_docs/docusaurus.config.js @@ -16,9 +16,6 @@ const config = { title: "🦜️🔗 Langchain", tagline: "LangChain JS Docs", favicon: "img/favicon.ico", - customFields: { - mendableAnonKey: process.env.MENDABLE_ANON_KEY, - }, // Set the production url of your site here url: "https://js.langchain.com", // Set the // pathname under which your site is served @@ -268,6 +265,18 @@ const config = { ], copyright: `Copyright © ${new Date().getFullYear()} LangChain, Inc.`, }, + algolia: { + // The application ID provided by Algolia + appId: "3EZV6U1TYC", + + // Public API key: it is safe to commit it + // this is linked to erick@langchain.dev currently + apiKey: "180851bbb9ba0ef6be9214849d6efeaf", + + indexName: "js-langchain", + + contextualSearch: true, + }, }), scripts: [ diff --git a/docs/core_docs/package.json b/docs/core_docs/package.json index 5b6c9a24684d..10a299ddedab 100644 --- a/docs/core_docs/package.json +++ b/docs/core_docs/package.json @@ -22,11 +22,10 @@ "format:check": "prettier --check \"**/*.{js,jsx,ts,tsx,md,mdx}\"" }, "dependencies": { - "@docusaurus/core": "2.4.1", - "@docusaurus/preset-classic": "2.4.1", - "@docusaurus/remark-plugin-npm2yarn": "^2.4.1", + "@docusaurus/core": "2.4.3", + "@docusaurus/preset-classic": "2.4.3", + "@docusaurus/remark-plugin-npm2yarn": "2.4.3", "@mdx-js/react": "^1.6.22", - "@mendable/search": "^0.0.160", "clsx": "^1.2.1", "json-loader": "^0.5.7", "process": "^0.11.10", diff --git a/docs/core_docs/src/css/custom.css b/docs/core_docs/src/css/custom.css index 64271eb80ff4..5bb97cb21118 100644 --- a/docs/core_docs/src/css/custom.css +++ b/docs/core_docs/src/css/custom.css @@ -36,25 +36,6 @@ --ifm-color-primary-lightest: #4fddbf; } -/* Reduce width on mobile for Mendable Search */ -@media (max-width: 767px) { - .mendable-search { - width: 200px; - } -} - -@media (max-width: 500px) { - .mendable-search { - width: 150px; - } -} - -@media (max-width: 380px) { - .mendable-search { - width: 140px; - } -} - .footer__links { margin-top: 1rem; margin-bottom: 3rem; diff --git a/docs/core_docs/src/theme/SearchBar.js b/docs/core_docs/src/theme/SearchBar.js deleted file mode 100644 index 3f2eb5fe6e28..000000000000 --- a/docs/core_docs/src/theme/SearchBar.js +++ /dev/null @@ -1,35 +0,0 @@ -/** - * Copyright (c) Meta Platforms, Inc. and affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - * - * @format - */ -import React from "react"; -import { MendableSearchBar } from "@mendable/search"; -import useDocusaurusContext from "@docusaurus/useDocusaurusContext"; - -export default function SearchBarWrapper() { - const { - siteConfig: { customFields }, - } = useDocusaurusContext(); - return ( -
- -
- ); -} diff --git a/yarn.lock b/yarn.lock index d1f3163cabf2..72680a1f35c7 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6073,9 +6073,9 @@ __metadata: languageName: node linkType: hard -"@docusaurus/core@npm:2.4.1": - version: 2.4.1 - resolution: "@docusaurus/core@npm:2.4.1" +"@docusaurus/core@npm:2.4.3": + version: 2.4.3 + resolution: "@docusaurus/core@npm:2.4.3" dependencies: "@babel/core": ^7.18.6 "@babel/generator": ^7.18.7 @@ -6087,13 +6087,13 @@ __metadata: "@babel/runtime": ^7.18.6 "@babel/runtime-corejs3": ^7.18.6 "@babel/traverse": ^7.18.8 - "@docusaurus/cssnano-preset": 2.4.1 - "@docusaurus/logger": 2.4.1 - "@docusaurus/mdx-loader": 2.4.1 + "@docusaurus/cssnano-preset": 2.4.3 + "@docusaurus/logger": 2.4.3 + "@docusaurus/mdx-loader": 2.4.3 "@docusaurus/react-loadable": 5.5.2 - "@docusaurus/utils": 2.4.1 - "@docusaurus/utils-common": 2.4.1 - "@docusaurus/utils-validation": 2.4.1 + "@docusaurus/utils": 2.4.3 + "@docusaurus/utils-common": 2.4.3 + "@docusaurus/utils-validation": 2.4.3 "@slorber/static-site-generator-webpack-plugin": ^4.0.7 "@svgr/webpack": ^6.2.1 autoprefixer: ^10.4.7 @@ -6153,40 +6153,40 @@ __metadata: react-dom: ^16.8.4 || ^17.0.0 bin: docusaurus: bin/docusaurus.mjs - checksum: 40c887ef662f7679d803695d4193268c2c177c6d4e13b43b56cc519322522a1608b4bfc4999f6355be778ca7a0256f0d27ab18a19b352a9da1aed66e2644dc82 + checksum: cce7173ee131364857c16f70f94155ba0e1b044cde54045fb0cf62ad138f8d8ef093f5aba7c7617a9aa0545b3ee3930aec2e09f645daec015696968338963013 languageName: node linkType: hard -"@docusaurus/cssnano-preset@npm:2.4.1": - version: 2.4.1 - resolution: "@docusaurus/cssnano-preset@npm:2.4.1" +"@docusaurus/cssnano-preset@npm:2.4.3": + version: 2.4.3 + resolution: "@docusaurus/cssnano-preset@npm:2.4.3" dependencies: cssnano-preset-advanced: ^5.3.8 postcss: ^8.4.14 postcss-sort-media-queries: ^4.2.1 tslib: ^2.4.0 - checksum: d498345981288af2dcb8650bed3c3361cfe336541a8bda65743fbe8ee5746e81e723ba086e2e6249c3b283f4bc50b5c81cff15b0406969cd610bed345b3804ac + checksum: f4a4c60b075c23541da90e00ae26af2e7eaadf20d783b37b9110a5e34599e4e91947425e33bad58ba71abee81c85cca99f5d7d76575f53fbaf73617b55e39c62 languageName: node linkType: hard -"@docusaurus/logger@npm:2.4.1": - version: 2.4.1 - resolution: "@docusaurus/logger@npm:2.4.1" +"@docusaurus/logger@npm:2.4.3": + version: 2.4.3 + resolution: "@docusaurus/logger@npm:2.4.3" dependencies: chalk: ^4.1.2 tslib: ^2.4.0 - checksum: be81840f2df477ab633d8ced6fd3a512582e764a48d66b1c12bb20b5d4c717f349e254e33b00b9b53381dbdb24a3e3d0ca9b19511366244b3620fa19cc4c69dc + checksum: f026a8233aa317f16ce5b25c6785a431f319c52fc07a1b9e26f4b3df2197974e75830a16b6140314f8f4ef02dc19242106ec2ae1599740b26d516cc34c56102f languageName: node linkType: hard -"@docusaurus/mdx-loader@npm:2.4.1": - version: 2.4.1 - resolution: "@docusaurus/mdx-loader@npm:2.4.1" +"@docusaurus/mdx-loader@npm:2.4.3": + version: 2.4.3 + resolution: "@docusaurus/mdx-loader@npm:2.4.3" dependencies: "@babel/parser": ^7.18.8 "@babel/traverse": ^7.18.8 - "@docusaurus/logger": 2.4.1 - "@docusaurus/utils": 2.4.1 + "@docusaurus/logger": 2.4.3 + "@docusaurus/utils": 2.4.3 "@mdx-js/mdx": ^1.6.22 escape-html: ^1.0.3 file-loader: ^6.2.0 @@ -6203,16 +6203,16 @@ __metadata: peerDependencies: react: ^16.8.4 || ^17.0.0 react-dom: ^16.8.4 || ^17.0.0 - checksum: cf36bbde228a058869dfd770a85f130035a54e563b957a3cfc3191d06efdcfc272bb51b51e6225a0246b233e5d7d0ca1cb4df4b700b837aa72bbb0c9f6f6f5bd + checksum: 5a774f7ea5f484e888b2bd1bf8b182279e3788afec779eb8920cf468b92ab8d83a1ae8be51925074241a4d1a38d989cfb366d2baf0f67ed6f063342395a7ca8e languageName: node linkType: hard -"@docusaurus/module-type-aliases@npm:2.4.1": - version: 2.4.1 - resolution: "@docusaurus/module-type-aliases@npm:2.4.1" +"@docusaurus/module-type-aliases@npm:2.4.3": + version: 2.4.3 + resolution: "@docusaurus/module-type-aliases@npm:2.4.3" dependencies: "@docusaurus/react-loadable": 5.5.2 - "@docusaurus/types": 2.4.1 + "@docusaurus/types": 2.4.3 "@types/history": ^4.7.11 "@types/react": "*" "@types/react-router-config": "*" @@ -6222,21 +6222,21 @@ __metadata: peerDependencies: react: "*" react-dom: "*" - checksum: 9e328c7bc5cd40b399550995edbeeea5ce88be7eb75f4c49499e8fd05a8bbabf180dce4d1cae0185721629fc6e0f2e8fc513e3ce846080f9771f7a9bc1c45ba8 + checksum: 22ce1a6a20acc35cdd2ec57e55f29e65dbe0fb3a46aaa8c033ec78bf04cd3087f0523c816c744ed311095512dd686c83e0a8619cc1a2a937c27cd54527739c38 languageName: node linkType: hard -"@docusaurus/plugin-content-blog@npm:2.4.1": - version: 2.4.1 - resolution: "@docusaurus/plugin-content-blog@npm:2.4.1" - dependencies: - "@docusaurus/core": 2.4.1 - "@docusaurus/logger": 2.4.1 - "@docusaurus/mdx-loader": 2.4.1 - "@docusaurus/types": 2.4.1 - "@docusaurus/utils": 2.4.1 - "@docusaurus/utils-common": 2.4.1 - "@docusaurus/utils-validation": 2.4.1 +"@docusaurus/plugin-content-blog@npm:2.4.3": + version: 2.4.3 + resolution: "@docusaurus/plugin-content-blog@npm:2.4.3" + dependencies: + "@docusaurus/core": 2.4.3 + "@docusaurus/logger": 2.4.3 + "@docusaurus/mdx-loader": 2.4.3 + "@docusaurus/types": 2.4.3 + "@docusaurus/utils": 2.4.3 + "@docusaurus/utils-common": 2.4.3 + "@docusaurus/utils-validation": 2.4.3 cheerio: ^1.0.0-rc.12 feed: ^4.2.2 fs-extra: ^10.1.0 @@ -6249,21 +6249,21 @@ __metadata: peerDependencies: react: ^16.8.4 || ^17.0.0 react-dom: ^16.8.4 || ^17.0.0 - checksum: 9d4e543b70d032d7edf0c986c45f36a088db76dc737a24374dcb877177b889fb0a5ed7b0a9c9ebb912523ef23ba26787d0fff59d9032b3e8075bdde9c072956a + checksum: 9fd41331c609b9488eea363e617e3763a814c75f83eb1b858cef402a0f5b96f67a342e25ff8c333489e550eb4d379eae09a88b986a97c25170fe203662e2f1ae languageName: node linkType: hard -"@docusaurus/plugin-content-docs@npm:2.4.1": - version: 2.4.1 - resolution: "@docusaurus/plugin-content-docs@npm:2.4.1" - dependencies: - "@docusaurus/core": 2.4.1 - "@docusaurus/logger": 2.4.1 - "@docusaurus/mdx-loader": 2.4.1 - "@docusaurus/module-type-aliases": 2.4.1 - "@docusaurus/types": 2.4.1 - "@docusaurus/utils": 2.4.1 - "@docusaurus/utils-validation": 2.4.1 +"@docusaurus/plugin-content-docs@npm:2.4.3": + version: 2.4.3 + resolution: "@docusaurus/plugin-content-docs@npm:2.4.3" + dependencies: + "@docusaurus/core": 2.4.3 + "@docusaurus/logger": 2.4.3 + "@docusaurus/mdx-loader": 2.4.3 + "@docusaurus/module-type-aliases": 2.4.3 + "@docusaurus/types": 2.4.3 + "@docusaurus/utils": 2.4.3 + "@docusaurus/utils-validation": 2.4.3 "@types/react-router-config": ^5.0.6 combine-promises: ^1.1.0 fs-extra: ^10.1.0 @@ -6276,132 +6276,132 @@ __metadata: peerDependencies: react: ^16.8.4 || ^17.0.0 react-dom: ^16.8.4 || ^17.0.0 - checksum: 028eda178dc81a74c25fd2efddb47e44451af2b268b13d99ef2b60cf13da1443f3bce884fd4a8a7ae92fed8ef747308309074f9524753fd80a40b5252a237e37 + checksum: bc01201f64721131eb84f264e51c7497b8034d2a3d99d762169f5dc456c3d8882acfa01fdbaa8fdc6e2e220479b36e0c9e8e17397bf887884589535bdeaeb4bb languageName: node linkType: hard -"@docusaurus/plugin-content-pages@npm:2.4.1": - version: 2.4.1 - resolution: "@docusaurus/plugin-content-pages@npm:2.4.1" +"@docusaurus/plugin-content-pages@npm:2.4.3": + version: 2.4.3 + resolution: "@docusaurus/plugin-content-pages@npm:2.4.3" dependencies: - "@docusaurus/core": 2.4.1 - "@docusaurus/mdx-loader": 2.4.1 - "@docusaurus/types": 2.4.1 - "@docusaurus/utils": 2.4.1 - "@docusaurus/utils-validation": 2.4.1 + "@docusaurus/core": 2.4.3 + "@docusaurus/mdx-loader": 2.4.3 + "@docusaurus/types": 2.4.3 + "@docusaurus/utils": 2.4.3 + "@docusaurus/utils-validation": 2.4.3 fs-extra: ^10.1.0 tslib: ^2.4.0 webpack: ^5.73.0 peerDependencies: react: ^16.8.4 || ^17.0.0 react-dom: ^16.8.4 || ^17.0.0 - checksum: 6af4eb7c064ed90158ad584eb64593473940b1880034a65fbcfde36116d6702b882bb9b0340141a5a48e67b0f84c03b8202b94171f5924d9f0c279cb68959a47 + checksum: 00439c2e1a1f345cd549739db13a3610b6d9f7ffa6cf7507ad6ac1f3c8d24041947acc2a446be7edf1a613cf354a50d1133aa28ddf64a0eff6ed8a31bf1a542f languageName: node linkType: hard -"@docusaurus/plugin-debug@npm:2.4.1": - version: 2.4.1 - resolution: "@docusaurus/plugin-debug@npm:2.4.1" +"@docusaurus/plugin-debug@npm:2.4.3": + version: 2.4.3 + resolution: "@docusaurus/plugin-debug@npm:2.4.3" dependencies: - "@docusaurus/core": 2.4.1 - "@docusaurus/types": 2.4.1 - "@docusaurus/utils": 2.4.1 + "@docusaurus/core": 2.4.3 + "@docusaurus/types": 2.4.3 + "@docusaurus/utils": 2.4.3 fs-extra: ^10.1.0 react-json-view: ^1.21.3 tslib: ^2.4.0 peerDependencies: react: ^16.8.4 || ^17.0.0 react-dom: ^16.8.4 || ^17.0.0 - checksum: 0be51e9a881383ed76b6e8f369ca6f7754a4f6bd59093c6d28d955b9422a25e868f24b534eb08ba84a5524ae742edd4a052813767b2ea1e8767914dceffc19b8 + checksum: 88955828b72e463e04501cc6bedf802208e377ae0f4d72735625bcbb47918afc4f2588355c6914064cfdbe4945d3da6473ce76319aa1f66dd975b3b43c4c39b0 languageName: node linkType: hard -"@docusaurus/plugin-google-analytics@npm:2.4.1": - version: 2.4.1 - resolution: "@docusaurus/plugin-google-analytics@npm:2.4.1" +"@docusaurus/plugin-google-analytics@npm:2.4.3": + version: 2.4.3 + resolution: "@docusaurus/plugin-google-analytics@npm:2.4.3" dependencies: - "@docusaurus/core": 2.4.1 - "@docusaurus/types": 2.4.1 - "@docusaurus/utils-validation": 2.4.1 + "@docusaurus/core": 2.4.3 + "@docusaurus/types": 2.4.3 + "@docusaurus/utils-validation": 2.4.3 tslib: ^2.4.0 peerDependencies: react: ^16.8.4 || ^17.0.0 react-dom: ^16.8.4 || ^17.0.0 - checksum: 9e754c0bc7779867af07cd77de36f5b491671a96fba5e3517458803465c24773357eb2f1400c41c80e69524cb2c7e9e353262335051aa54192eeae9d9eb055cb + checksum: 6e30de6b5c479493614a5552a295f07ffb9c83f3740a68c7d4dbac378b8288da7430f26cdc246d763855c6084ad86a6f87286e6c8b40f4817794bb1a04e109ea languageName: node linkType: hard -"@docusaurus/plugin-google-gtag@npm:2.4.1": - version: 2.4.1 - resolution: "@docusaurus/plugin-google-gtag@npm:2.4.1" +"@docusaurus/plugin-google-gtag@npm:2.4.3": + version: 2.4.3 + resolution: "@docusaurus/plugin-google-gtag@npm:2.4.3" dependencies: - "@docusaurus/core": 2.4.1 - "@docusaurus/types": 2.4.1 - "@docusaurus/utils-validation": 2.4.1 + "@docusaurus/core": 2.4.3 + "@docusaurus/types": 2.4.3 + "@docusaurus/utils-validation": 2.4.3 tslib: ^2.4.0 peerDependencies: react: ^16.8.4 || ^17.0.0 react-dom: ^16.8.4 || ^17.0.0 - checksum: ed529f2100599401e1c2aa772dca7c60fdb1990e44af3a9e476e1922f1370ef0dd0b5e6442f846bd942b74af63f3163ac85f1eefe1e85660b61ee60f2044c463 + checksum: 4aaac4d262b3bb7fc3f16620c5329b90db92bf28361ced54f2945fc0e4669483e2f36b076332e0ee9d11b6233cd2c81ca35c953119bad42171e62571c1692d6a languageName: node linkType: hard -"@docusaurus/plugin-google-tag-manager@npm:2.4.1": - version: 2.4.1 - resolution: "@docusaurus/plugin-google-tag-manager@npm:2.4.1" +"@docusaurus/plugin-google-tag-manager@npm:2.4.3": + version: 2.4.3 + resolution: "@docusaurus/plugin-google-tag-manager@npm:2.4.3" dependencies: - "@docusaurus/core": 2.4.1 - "@docusaurus/types": 2.4.1 - "@docusaurus/utils-validation": 2.4.1 + "@docusaurus/core": 2.4.3 + "@docusaurus/types": 2.4.3 + "@docusaurus/utils-validation": 2.4.3 tslib: ^2.4.0 peerDependencies: react: ^16.8.4 || ^17.0.0 react-dom: ^16.8.4 || ^17.0.0 - checksum: c5c6fce9c9eeae7cbeb277b9765a67d5c4403a3e04f634aadac6d4ba9901739a547d4ea023c83a76cfece2fdb2d175851acaa69abb2f190401b612adeab5524d + checksum: c3af89b4d41fab463d853cbfbe8f43d384f702dd09fd914fffcca01fdf94c282d1b98d762c9142fe21f6471f5dd643679e8d11344c95fdf6657aff0618c3c7a5 languageName: node linkType: hard -"@docusaurus/plugin-sitemap@npm:2.4.1": - version: 2.4.1 - resolution: "@docusaurus/plugin-sitemap@npm:2.4.1" - dependencies: - "@docusaurus/core": 2.4.1 - "@docusaurus/logger": 2.4.1 - "@docusaurus/types": 2.4.1 - "@docusaurus/utils": 2.4.1 - "@docusaurus/utils-common": 2.4.1 - "@docusaurus/utils-validation": 2.4.1 +"@docusaurus/plugin-sitemap@npm:2.4.3": + version: 2.4.3 + resolution: "@docusaurus/plugin-sitemap@npm:2.4.3" + dependencies: + "@docusaurus/core": 2.4.3 + "@docusaurus/logger": 2.4.3 + "@docusaurus/types": 2.4.3 + "@docusaurus/utils": 2.4.3 + "@docusaurus/utils-common": 2.4.3 + "@docusaurus/utils-validation": 2.4.3 fs-extra: ^10.1.0 sitemap: ^7.1.1 tslib: ^2.4.0 peerDependencies: react: ^16.8.4 || ^17.0.0 react-dom: ^16.8.4 || ^17.0.0 - checksum: aa6728278017c047b4ed1456e349b1a267d85b4bb0c422bb63e59fc28ccb0e286dc66918f44f6ade660e550b047e2b14796c54c96fde6eb69395770cf39cf306 + checksum: cf96b9f0e32cefa58e37a4bc2f0a112ea657f06faf47b780ec2ba39d5e2daca6486a73f3b376c56ad3bb42f3f0c3f70a783f1ce1964b74e2ba273e6f439e439b languageName: node linkType: hard -"@docusaurus/preset-classic@npm:2.4.1": - version: 2.4.1 - resolution: "@docusaurus/preset-classic@npm:2.4.1" - dependencies: - "@docusaurus/core": 2.4.1 - "@docusaurus/plugin-content-blog": 2.4.1 - "@docusaurus/plugin-content-docs": 2.4.1 - "@docusaurus/plugin-content-pages": 2.4.1 - "@docusaurus/plugin-debug": 2.4.1 - "@docusaurus/plugin-google-analytics": 2.4.1 - "@docusaurus/plugin-google-gtag": 2.4.1 - "@docusaurus/plugin-google-tag-manager": 2.4.1 - "@docusaurus/plugin-sitemap": 2.4.1 - "@docusaurus/theme-classic": 2.4.1 - "@docusaurus/theme-common": 2.4.1 - "@docusaurus/theme-search-algolia": 2.4.1 - "@docusaurus/types": 2.4.1 +"@docusaurus/preset-classic@npm:2.4.3": + version: 2.4.3 + resolution: "@docusaurus/preset-classic@npm:2.4.3" + dependencies: + "@docusaurus/core": 2.4.3 + "@docusaurus/plugin-content-blog": 2.4.3 + "@docusaurus/plugin-content-docs": 2.4.3 + "@docusaurus/plugin-content-pages": 2.4.3 + "@docusaurus/plugin-debug": 2.4.3 + "@docusaurus/plugin-google-analytics": 2.4.3 + "@docusaurus/plugin-google-gtag": 2.4.3 + "@docusaurus/plugin-google-tag-manager": 2.4.3 + "@docusaurus/plugin-sitemap": 2.4.3 + "@docusaurus/theme-classic": 2.4.3 + "@docusaurus/theme-common": 2.4.3 + "@docusaurus/theme-search-algolia": 2.4.3 + "@docusaurus/types": 2.4.3 peerDependencies: react: ^16.8.4 || ^17.0.0 react-dom: ^16.8.4 || ^17.0.0 - checksum: bad7f237ac03a9bc6206cb7a5d077d85d5a6316d34ff089c487ce92b8f6103ef22b04f35d637bdc31dddabd97d5babb1817852b9b97db7c33f3d4c7f33cb149d + checksum: a321badc44696adf4ab2d4a5d6c93f595e8c17988aec9609d325928a1d60f5e0205b23fe849b28ddaed24f7935829e86c402f6b761d6e65db4224270b9dd443c languageName: node linkType: hard @@ -6417,7 +6417,7 @@ __metadata: languageName: node linkType: hard -"@docusaurus/remark-plugin-npm2yarn@npm:^2.4.1": +"@docusaurus/remark-plugin-npm2yarn@npm:2.4.3": version: 2.4.3 resolution: "@docusaurus/remark-plugin-npm2yarn@npm:2.4.3" dependencies: @@ -6428,22 +6428,22 @@ __metadata: languageName: node linkType: hard -"@docusaurus/theme-classic@npm:2.4.1": - version: 2.4.1 - resolution: "@docusaurus/theme-classic@npm:2.4.1" - dependencies: - "@docusaurus/core": 2.4.1 - "@docusaurus/mdx-loader": 2.4.1 - "@docusaurus/module-type-aliases": 2.4.1 - "@docusaurus/plugin-content-blog": 2.4.1 - "@docusaurus/plugin-content-docs": 2.4.1 - "@docusaurus/plugin-content-pages": 2.4.1 - "@docusaurus/theme-common": 2.4.1 - "@docusaurus/theme-translations": 2.4.1 - "@docusaurus/types": 2.4.1 - "@docusaurus/utils": 2.4.1 - "@docusaurus/utils-common": 2.4.1 - "@docusaurus/utils-validation": 2.4.1 +"@docusaurus/theme-classic@npm:2.4.3": + version: 2.4.3 + resolution: "@docusaurus/theme-classic@npm:2.4.3" + dependencies: + "@docusaurus/core": 2.4.3 + "@docusaurus/mdx-loader": 2.4.3 + "@docusaurus/module-type-aliases": 2.4.3 + "@docusaurus/plugin-content-blog": 2.4.3 + "@docusaurus/plugin-content-docs": 2.4.3 + "@docusaurus/plugin-content-pages": 2.4.3 + "@docusaurus/theme-common": 2.4.3 + "@docusaurus/theme-translations": 2.4.3 + "@docusaurus/types": 2.4.3 + "@docusaurus/utils": 2.4.3 + "@docusaurus/utils-common": 2.4.3 + "@docusaurus/utils-validation": 2.4.3 "@mdx-js/react": ^1.6.22 clsx: ^1.2.1 copy-text-to-clipboard: ^3.0.1 @@ -6460,21 +6460,21 @@ __metadata: peerDependencies: react: ^16.8.4 || ^17.0.0 react-dom: ^16.8.4 || ^17.0.0 - checksum: 058875d4c60f77f86b5d679b1ef99ed06101411f003d2d65fa4fe5ae6fbe5e5e6a291616268a18a29fdd84f0853cc4219a2c1801663b75f27c664b3ace7d009e + checksum: 215b7fa416f40ce68773265a168af47fa770583ebe33ec7b34c7e082dfe7c79252b589a6b26532cb0ab7dd089611a9cd0e20c94df097be320a227b98e3b3fbb8 languageName: node linkType: hard -"@docusaurus/theme-common@npm:2.4.1": - version: 2.4.1 - resolution: "@docusaurus/theme-common@npm:2.4.1" - dependencies: - "@docusaurus/mdx-loader": 2.4.1 - "@docusaurus/module-type-aliases": 2.4.1 - "@docusaurus/plugin-content-blog": 2.4.1 - "@docusaurus/plugin-content-docs": 2.4.1 - "@docusaurus/plugin-content-pages": 2.4.1 - "@docusaurus/utils": 2.4.1 - "@docusaurus/utils-common": 2.4.1 +"@docusaurus/theme-common@npm:2.4.3": + version: 2.4.3 + resolution: "@docusaurus/theme-common@npm:2.4.3" + dependencies: + "@docusaurus/mdx-loader": 2.4.3 + "@docusaurus/module-type-aliases": 2.4.3 + "@docusaurus/plugin-content-blog": 2.4.3 + "@docusaurus/plugin-content-docs": 2.4.3 + "@docusaurus/plugin-content-pages": 2.4.3 + "@docusaurus/utils": 2.4.3 + "@docusaurus/utils-common": 2.4.3 "@types/history": ^4.7.11 "@types/react": "*" "@types/react-router-config": "*" @@ -6487,22 +6487,22 @@ __metadata: peerDependencies: react: ^16.8.4 || ^17.0.0 react-dom: ^16.8.4 || ^17.0.0 - checksum: 206db83caab59eadc5b8e5394d46b64ec8695bd20d4a3defe111c28094faf6de92481c3bb4e54c159a519bc782759031b121e17d7e0175d873a843f36630c539 + checksum: 76817f548705542124d708c804e724674ec9bf996a5cb2a5c9a2919416367567cca4a3fa6055589990c339f6e1fb9d3944e25ed30b79fabe191db00d6ef986ca languageName: node linkType: hard -"@docusaurus/theme-search-algolia@npm:2.4.1": - version: 2.4.1 - resolution: "@docusaurus/theme-search-algolia@npm:2.4.1" +"@docusaurus/theme-search-algolia@npm:2.4.3": + version: 2.4.3 + resolution: "@docusaurus/theme-search-algolia@npm:2.4.3" dependencies: "@docsearch/react": ^3.1.1 - "@docusaurus/core": 2.4.1 - "@docusaurus/logger": 2.4.1 - "@docusaurus/plugin-content-docs": 2.4.1 - "@docusaurus/theme-common": 2.4.1 - "@docusaurus/theme-translations": 2.4.1 - "@docusaurus/utils": 2.4.1 - "@docusaurus/utils-validation": 2.4.1 + "@docusaurus/core": 2.4.3 + "@docusaurus/logger": 2.4.3 + "@docusaurus/plugin-content-docs": 2.4.3 + "@docusaurus/theme-common": 2.4.3 + "@docusaurus/theme-translations": 2.4.3 + "@docusaurus/utils": 2.4.3 + "@docusaurus/utils-validation": 2.4.3 algoliasearch: ^4.13.1 algoliasearch-helper: ^3.10.0 clsx: ^1.2.1 @@ -6514,23 +6514,23 @@ __metadata: peerDependencies: react: ^16.8.4 || ^17.0.0 react-dom: ^16.8.4 || ^17.0.0 - checksum: 00016804462e3ca961de96f477c397bf68bbfa7c641cfb95e76492ec00f2e0f8f5b19623cd6ad0fda31ad08aa29fa1a74185d9bd34f61437e7f36f711064f3ba + checksum: 665d244c25bff21dd45c983c9b85f9827d2dd58945b802d645370b5e7092820532faf488c0bc0ce88e8fc0088c7f56eb9abb96589cf3857372c1b61bba6cbed7 languageName: node linkType: hard -"@docusaurus/theme-translations@npm:2.4.1": - version: 2.4.1 - resolution: "@docusaurus/theme-translations@npm:2.4.1" +"@docusaurus/theme-translations@npm:2.4.3": + version: 2.4.3 + resolution: "@docusaurus/theme-translations@npm:2.4.3" dependencies: fs-extra: ^10.1.0 tslib: ^2.4.0 - checksum: cf21cd01db6426ccc29360fe9caca39e61ee5efde3796539e8292e212c25727227970f935050f294f0ab475f201720e32a1d09a7e40f2b08f56f69282f660da8 + checksum: 8424583a130b0d32b6adf578dc5daeefaad199019c8a6a23fbd67577209be64923cde59d423ea9d41d6e7cfc2318e7fa6a17a665e8ae1c871ce0880525f9b8fd languageName: node linkType: hard -"@docusaurus/types@npm:2.4.1": - version: 2.4.1 - resolution: "@docusaurus/types@npm:2.4.1" +"@docusaurus/types@npm:2.4.3": + version: 2.4.3 + resolution: "@docusaurus/types@npm:2.4.3" dependencies: "@types/history": ^4.7.11 "@types/react": "*" @@ -6543,13 +6543,13 @@ __metadata: peerDependencies: react: ^16.8.4 || ^17.0.0 react-dom: ^16.8.4 || ^17.0.0 - checksum: d44e91c9153802a5c63a0bd91e56654f901df837ac7b380dff8f165991728e88d29efa7c64c6522d0afdf8ec845613aff5867badb717d29b691392712f655936 + checksum: c123c45630e885b588f808baa06a97f8408a3381906f65cb92ae75732aedfca6ab2cada94f969c08e043b885b95298616440326259b789010e0986cbcd7a960b languageName: node linkType: hard -"@docusaurus/utils-common@npm:2.4.1": - version: 2.4.1 - resolution: "@docusaurus/utils-common@npm:2.4.1" +"@docusaurus/utils-common@npm:2.4.3": + version: 2.4.3 + resolution: "@docusaurus/utils-common@npm:2.4.3" dependencies: tslib: ^2.4.0 peerDependencies: @@ -6557,28 +6557,28 @@ __metadata: peerDependenciesMeta: "@docusaurus/types": optional: true - checksum: 475f05b94a879d9078564dbb081d91a953356f54d0a4384a8711281a62851b58d99df9b45664a30e40ac37733772cedd53a253d34a07fbd5b36bcce46ab200c8 + checksum: 1ae315d8d8ce7a0163a698ffdca55b734d21f336512138c128bc0fa2a8d224edbaad0c8dbd7a3de2e8ef734dc2656c505d09066dee4fc84819d153593abb8984 languageName: node linkType: hard -"@docusaurus/utils-validation@npm:2.4.1": - version: 2.4.1 - resolution: "@docusaurus/utils-validation@npm:2.4.1" +"@docusaurus/utils-validation@npm:2.4.3": + version: 2.4.3 + resolution: "@docusaurus/utils-validation@npm:2.4.3" dependencies: - "@docusaurus/logger": 2.4.1 - "@docusaurus/utils": 2.4.1 + "@docusaurus/logger": 2.4.3 + "@docusaurus/utils": 2.4.3 joi: ^17.6.0 js-yaml: ^4.1.0 tslib: ^2.4.0 - checksum: 44dc482770ea3932e68e58c0bb9503e1b3c73ce28565c438b0d68a7427ee91760ca84a5e150dfc4e04497e072fe4394050dd2af4c4ff43a227b1464e89d705a0 + checksum: d3472b3f7a0a029c2cef1f00bc9db403d5f7e74e2091eccbc45d06f5776a84fd73bd1a18cf3a8a3cc0348ce49f753a1300deac670c2a82c56070cc40ca9df06e languageName: node linkType: hard -"@docusaurus/utils@npm:2.4.1": - version: 2.4.1 - resolution: "@docusaurus/utils@npm:2.4.1" +"@docusaurus/utils@npm:2.4.3": + version: 2.4.3 + resolution: "@docusaurus/utils@npm:2.4.3" dependencies: - "@docusaurus/logger": 2.4.1 + "@docusaurus/logger": 2.4.3 "@svgr/webpack": ^6.2.1 escape-string-regexp: ^4.0.0 file-loader: ^6.2.0 @@ -6599,7 +6599,7 @@ __metadata: peerDependenciesMeta: "@docusaurus/types": optional: true - checksum: 4c7e49cabe6650b027c0f698344532fb81c8aaf912f17a287fad986e008ce7c6d34d372b44974488ce160ae43ef6aad4ac7b2790bc3fe2ab05ef5fa7b9506ce9 + checksum: dd1aa7688d1a4b2775e13a91d528608ceab33c57a921404d9a989867c31c8ef17fe3892e4f5680dfb4a783da7b9973e2077e907ff4ac172927433e606e8fa9b9 languageName: node linkType: hard @@ -8056,11 +8056,10 @@ __metadata: resolution: "@langchain/core_docs@workspace:docs/core_docs" dependencies: "@babel/eslint-parser": ^7.18.2 - "@docusaurus/core": 2.4.1 - "@docusaurus/preset-classic": 2.4.1 - "@docusaurus/remark-plugin-npm2yarn": ^2.4.1 + "@docusaurus/core": 2.4.3 + "@docusaurus/preset-classic": 2.4.3 + "@docusaurus/remark-plugin-npm2yarn": 2.4.3 "@mdx-js/react": ^1.6.22 - "@mendable/search": ^0.0.160 "@swc/core": ^1.3.62 clsx: ^1.2.1 docusaurus-plugin-typedoc: 1.0.0-next.5 @@ -8202,19 +8201,6 @@ __metadata: languageName: node linkType: hard -"@mendable/search@npm:^0.0.160": - version: 0.0.160 - resolution: "@mendable/search@npm:0.0.160" - dependencies: - html-react-parser: ^4.2.0 - posthog-js: ^1.45.1 - peerDependencies: - react: ^17.x || ^18.x - react-dom: ^17.x || ^18.x - checksum: a0cab8f81b0b15c959f460032aea0ad7a5c2e6d97ca9b4dbff5c170fbf0990b25e2764cce0598f7750d7296479703cfb99c073b74eb258b209c63cb3b7f1afd2 - languageName: node - linkType: hard - "@mozilla/readability@npm:^0.4.4": version: 0.4.4 resolution: "@mozilla/readability@npm:0.4.4" @@ -16527,15 +16513,6 @@ __metadata: languageName: node linkType: hard -"domhandler@npm:5.0.3, domhandler@npm:^5.0.1, domhandler@npm:^5.0.2, domhandler@npm:^5.0.3": - version: 5.0.3 - resolution: "domhandler@npm:5.0.3" - dependencies: - domelementtype: ^2.3.0 - checksum: 0f58f4a6af63e6f3a4320aa446d28b5790a009018707bce2859dcb1d21144c7876482b5188395a188dfa974238c019e0a1e610d2fc269a12b2c192ea2b0b131c - languageName: node - linkType: hard - "domhandler@npm:^4.0.0, domhandler@npm:^4.2.0, domhandler@npm:^4.3.1": version: 4.3.1 resolution: "domhandler@npm:4.3.1" @@ -16545,6 +16522,15 @@ __metadata: languageName: node linkType: hard +"domhandler@npm:^5.0.1, domhandler@npm:^5.0.2, domhandler@npm:^5.0.3": + version: 5.0.3 + resolution: "domhandler@npm:5.0.3" + dependencies: + domelementtype: ^2.3.0 + checksum: 0f58f4a6af63e6f3a4320aa446d28b5790a009018707bce2859dcb1d21144c7876482b5188395a188dfa974238c019e0a1e610d2fc269a12b2c192ea2b0b131c + languageName: node + linkType: hard + "domutils@npm:^2.5.2, domutils@npm:^2.8.0": version: 2.8.0 resolution: "domutils@npm:2.8.0" @@ -16567,17 +16553,6 @@ __metadata: languageName: node linkType: hard -"domutils@npm:^3.1.0": - version: 3.1.0 - resolution: "domutils@npm:3.1.0" - dependencies: - dom-serializer: ^2.0.0 - domelementtype: ^2.3.0 - domhandler: ^5.0.3 - checksum: e5757456ddd173caa411cfc02c2bb64133c65546d2c4081381a3bafc8a57411a41eed70494551aa58030be9e58574fcc489828bebd673863d39924fb4878f416 - languageName: node - linkType: hard - "dot-case@npm:^3.0.4": version: 3.0.4 resolution: "dot-case@npm:3.0.4" @@ -16849,13 +16824,6 @@ __metadata: languageName: node linkType: hard -"entities@npm:^4.5.0": - version: 4.5.0 - resolution: "entities@npm:4.5.0" - checksum: 853f8ebd5b425d350bffa97dd6958143179a5938352ccae092c62d1267c4e392a039be1bae7d51b6e4ffad25f51f9617531fedf5237f15df302ccfb452cbf2d7 - languageName: node - linkType: hard - "entities@npm:~2.1.0": version: 2.1.0 resolution: "entities@npm:2.1.0" @@ -18606,13 +18574,6 @@ __metadata: languageName: node linkType: hard -"fflate@npm:^0.4.1": - version: 0.4.8 - resolution: "fflate@npm:0.4.8" - checksum: 29d8cbe44d5e7f53e7f5a160ac7f9cc025480c7b3bfd85c5f898cbe20dfa2dad4732daa534982664bf30b35896a90af44ea33ede5d94c5ffd1b8b0c0a0a56ca2 - languageName: node - linkType: hard - "figures@npm:^5.0.0": version: 5.0.0 resolution: "figures@npm:5.0.0" @@ -20198,16 +20159,6 @@ __metadata: languageName: node linkType: hard -"html-dom-parser@npm:4.0.1": - version: 4.0.1 - resolution: "html-dom-parser@npm:4.0.1" - dependencies: - domhandler: 5.0.3 - htmlparser2: 9.0.0 - checksum: a40689030db11808c33b57c09c52917b201de8346d4dc5924ec102f9698b4ed33f204e6a294bc7e9cfabadf770db3e1d3a9e0ff79690442866ee6910a1a0d64a - languageName: node - linkType: hard - "html-encoding-sniffer@npm:^3.0.0": version: 3.0.0 resolution: "html-encoding-sniffer@npm:3.0.0" @@ -20255,20 +20206,6 @@ __metadata: languageName: node linkType: hard -"html-react-parser@npm:^4.2.0": - version: 4.2.3 - resolution: "html-react-parser@npm:4.2.3" - dependencies: - domhandler: 5.0.3 - html-dom-parser: 4.0.1 - react-property: 2.0.0 - style-to-js: 1.1.4 - peerDependencies: - react: 0.14 || 15 || 16 || 17 || 18 - checksum: 1a87bab53a45407a85a65714d338f7800c57434e6153da80f75d4784c662a8a3aeecb3874d9c54e78a5c183043fb14a7b889d7685dca36a2a8c6fe7ebcf0e913 - languageName: node - linkType: hard - "html-tags@npm:^3.2.0": version: 3.3.1 resolution: "html-tags@npm:3.3.1" @@ -20311,18 +20248,6 @@ __metadata: languageName: node linkType: hard -"htmlparser2@npm:9.0.0": - version: 9.0.0 - resolution: "htmlparser2@npm:9.0.0" - dependencies: - domelementtype: ^2.3.0 - domhandler: ^5.0.3 - domutils: ^3.1.0 - entities: ^4.5.0 - checksum: a234c3add821cae8308ca61ce4b8ad3e88af83cf9c3c2003059adc89c46a06ffc39cc2a92b39af8d16c3705e1055df6769d80877acb6529983867f0d7e74098d - languageName: node - linkType: hard - "htmlparser2@npm:^6.1.0": version: 6.1.0 resolution: "htmlparser2@npm:6.1.0" @@ -26960,15 +26885,6 @@ __metadata: languageName: node linkType: hard -"posthog-js@npm:^1.45.1": - version: 1.83.1 - resolution: "posthog-js@npm:1.83.1" - dependencies: - fflate: ^0.4.1 - checksum: dad6c85846c2cad39800abae0a51e9fffd16ae148b7aa22cdb68b36911267679e10b0d6d03f2955bafbb6b41f987ca46ae1825bc437d55f64375465b73346269 - languageName: node - linkType: hard - "prebuild-install@npm:^7.1.1": version: 7.1.1 resolution: "prebuild-install@npm:7.1.1" @@ -27761,13 +27677,6 @@ __metadata: languageName: node linkType: hard -"react-property@npm:2.0.0": - version: 2.0.0 - resolution: "react-property@npm:2.0.0" - checksum: 8a444df30ef0937689c7968dae2501a0ca523777169f450e1f7ef5beeb855d6509bd058bf612f6ed8f459aa35468335d356e50264492e1938586e59fdb988262 - languageName: node - linkType: hard - "react-reconciler@npm:0.28.0": version: 0.28.0 resolution: "react-reconciler@npm:0.28.0" @@ -29958,15 +29867,6 @@ __metadata: languageName: node linkType: hard -"style-to-js@npm:1.1.4": - version: 1.1.4 - resolution: "style-to-js@npm:1.1.4" - dependencies: - style-to-object: 0.4.2 - checksum: 0ed2b3400fb602f9ab6d024fb2ff23630e2f2cbdd98150e13ba16a2d9b46d7213b5e71fde15cef30b7e24f336f87ef6e8c7a29b520ff8c6a1dca92f8762e7358 - languageName: node - linkType: hard - "style-to-object@npm:0.3.0, style-to-object@npm:^0.3.0": version: 0.3.0 resolution: "style-to-object@npm:0.3.0" @@ -29976,15 +29876,6 @@ __metadata: languageName: node linkType: hard -"style-to-object@npm:0.4.2": - version: 0.4.2 - resolution: "style-to-object@npm:0.4.2" - dependencies: - inline-style-parser: 0.1.1 - checksum: 314a80bcfadde41c2b9c8d717a4b1f2220954561040c2c7740496715da5cb95f99920a8eeefe2d4a862149875f352a12eda9bbef5816d7e0a71910da00d1521f - languageName: node - linkType: hard - "styled-jsx@npm:5.1.1": version: 5.1.1 resolution: "styled-jsx@npm:5.1.1" From 03fa76ad796dba4e1b09f9415a5eb688195a1312 Mon Sep 17 00:00:00 2001 From: Uthman Mohamed <83053931+1239uth@users.noreply.github.com> Date: Mon, 4 Dec 2023 22:31:23 -0500 Subject: [PATCH 31/44] Add Gmail Tool (#3438) * Add GmailBaseTool * Add GmailGetMessage * Fix eslint formatting errors * fix: _call returns stringified output now * feat: create gmail draft message complete * fix: remove unused parameter * fix: removed unused import statement * fix: reformatted file and made method private * Add GmailGetThread * Fixes formatting issues * Fix _call error * Add GmailSearch * Fix build error on types * Create sent_message.ts * Update sent_message.ts run the prettier to format the document * Update sent_message.ts combine the sendMessage function into _call function * Move gmail object from children to parent GmailBaseTool * Fix formatting in gmail/base.ts * fix: switched to Buffer class for base64 encode * Make fields optional and use defaults properly in constructor Previously the default values weren't being used in the constructor, this commit fixes that. Also fields are now optional in each of the gmail tool constructors since they have defaults as backups anyways * Use Zod to parse input of GmailBaseTool constructor * Update zod schema to be entirely optional for GmailBaseToolParams * Create docs for Gmail Tool * Add comment for default parameters, fix formatting * Remove model from default parameters comment * Add relavent tools in gmail example * Add index.ts for all exports and rename send_message * Add unit tests for gmail tools * Add gmail type definitions to package.json * Update typedoc.json add gmail to typedoc.json * Update create-entrypoints.js add the entrypoints for gmail tool * add description for our function add example on our description * update .gitignore * fix the entrypoint * change order * change the zod * fix the format * Update base.ts fix lint problem * Update base.ts remove the unuse comment * add description for search * fix: gmail tools extend structured tool * Update descriptions.ts * fix: tree shaking issues with zod fixed * fix: prettier formatting * Add zod back to GmailBaseTool * Fix gmail example to work for StructuredTool * Add gmail API key setup instructions in docs * Fix formatting * Fix formatting * Replace .call with .invoke in gmail example * Update gmail.ts --------- Co-authored-by: Hamoon Zamiri Co-authored-by: saeedahsan Co-authored-by: SeannnX <122410542+SeannnX@users.noreply.github.com> Co-authored-by: Hamoon <90403097+HamoonZamiri@users.noreply.github.com> Co-authored-by: Ahsan Saeed Co-authored-by: Jacob Lee --- docs/api_refs/typedoc.json | 1 + .../docs/integrations/tools/gmail.mdx | 27 ++++ examples/src/tools/gmail.ts | 56 ++++++++ langchain/.gitignore | 3 + langchain/package.json | 8 ++ langchain/scripts/create-entrypoints.js | 7 +- langchain/src/load/import_constants.ts | 1 + langchain/src/load/import_type.d.ts | 3 + langchain/src/tools/gmail/base.ts | 75 ++++++++++ langchain/src/tools/gmail/create_draft.ts | 74 ++++++++++ langchain/src/tools/gmail/descriptions.ts | 119 +++++++++++++++ langchain/src/tools/gmail/get_message.ts | 95 ++++++++++++ langchain/src/tools/gmail/get_thread.ts | 105 ++++++++++++++ langchain/src/tools/gmail/index.ts | 12 ++ langchain/src/tools/gmail/search.ts | 135 ++++++++++++++++++ langchain/src/tools/gmail/send_message.ts | 84 +++++++++++ langchain/src/tools/tests/gmail.test.ts | 63 ++++++++ 17 files changed, 866 insertions(+), 2 deletions(-) create mode 100644 docs/core_docs/docs/integrations/tools/gmail.mdx create mode 100644 examples/src/tools/gmail.ts create mode 100644 langchain/src/tools/gmail/base.ts create mode 100644 langchain/src/tools/gmail/create_draft.ts create mode 100644 langchain/src/tools/gmail/descriptions.ts create mode 100644 langchain/src/tools/gmail/get_message.ts create mode 100644 langchain/src/tools/gmail/get_thread.ts create mode 100644 langchain/src/tools/gmail/index.ts create mode 100644 langchain/src/tools/gmail/search.ts create mode 100644 langchain/src/tools/gmail/send_message.ts create mode 100644 langchain/src/tools/tests/gmail.test.ts diff --git a/docs/api_refs/typedoc.json b/docs/api_refs/typedoc.json index c03de0c396a3..a7f8c8b24293 100644 --- a/docs/api_refs/typedoc.json +++ b/docs/api_refs/typedoc.json @@ -41,6 +41,7 @@ "./langchain/src/tools/render.ts", "./langchain/src/tools/sql.ts", "./langchain/src/tools/webbrowser.ts", + "./langchain/src/tools/gmail/index.ts", "./langchain/src/tools/google_calendar/index.ts", "./langchain/src/tools/google_places.ts", "./langchain/src/chains/index.ts", diff --git a/docs/core_docs/docs/integrations/tools/gmail.mdx b/docs/core_docs/docs/integrations/tools/gmail.mdx new file mode 100644 index 000000000000..2cd77f7ec97c --- /dev/null +++ b/docs/core_docs/docs/integrations/tools/gmail.mdx @@ -0,0 +1,27 @@ +--- +hide_table_of_contents: true +--- + +import CodeBlock from "@theme/CodeBlock"; + +# Gmail Tool + +The Gmail Tool allows your agent to create and view messages from a linked email account. + +## Setup + +You will need to get an API key from [Google here](https://developers.google.com/gmail/api/guides) +and [enable the new Gmail API](https://console.cloud.google.com/apis/library/gmail.googleapis.com). +Then, set the environment variables for `GMAIL_CLIENT_EMAIL`, and either `GMAIL_PRIVATE_KEY`, or `GMAIL_KEYFILE`. + +To use the Gmail Tool you need to install the following official peer dependency: + +```bash npm2yarn +npm install googleapis +``` + +## Usage + +import ToolExample from "@examples/tools/gmail.ts"; + +{ToolExample} diff --git a/examples/src/tools/gmail.ts b/examples/src/tools/gmail.ts new file mode 100644 index 000000000000..01c3c32ba558 --- /dev/null +++ b/examples/src/tools/gmail.ts @@ -0,0 +1,56 @@ +import { initializeAgentExecutorWithOptions } from "langchain/agents"; +import { OpenAI } from "langchain/llms/openai"; +import { StructuredTool } from "langchain/tools"; +import { + GmailCreateDraft, + GmailGetMessage, + GmailGetThread, + GmailSearch, + GmailSendMessage, +} from "langchain/tools/gmail"; + +export async function run() { + const model = new OpenAI({ + temperature: 0, + openAIApiKey: process.env.OPENAI_API_KEY, + }); + + // These are the default parameters for the Gmail tools + // const gmailParams = { + // credentials: { + // clientEmail: process.env.GMAIL_CLIENT_EMAIL, + // privateKey: process.env.GMAIL_PRIVATE_KEY, + // }, + // scopes: ["https://mail.google.com/"], + // }; + + // For custom parameters, uncomment the code above, replace the values with your own, and pass it to the tools below + const tools: StructuredTool[] = [ + new GmailCreateDraft(), + new GmailGetMessage(), + new GmailGetThread(), + new GmailSearch(), + new GmailSendMessage(), + ]; + + const gmailAgent = await initializeAgentExecutorWithOptions(tools, model, { + agentType: "structured-chat-zero-shot-react-description", + verbose: true, + }); + + const createInput = `Create a gmail draft for me to edit of a letter from the perspective of a sentient parrot who is looking to collaborate on some research with her estranged friend, a cat. Under no circumstances may you send the message, however.`; + + const createResult = await gmailAgent.invoke({ input: createInput }); + // Create Result { + // output: 'I have created a draft email for you to edit. The draft Id is r5681294731961864018.' + // } + console.log("Create Result", createResult); + + const viewInput = `Could you search in my drafts for the latest email?`; + + const viewResult = await gmailAgent.invoke({ input: viewInput }); + // View Result { + // output: "The latest email in your drafts is from hopefulparrot@gmail.com with the subject 'Collaboration Opportunity'. The body of the email reads: 'Dear [Friend], I hope this letter finds you well. I am writing to you in the hopes of rekindling our friendship and to discuss the possibility of collaborating on some research together. I know that we have had our differences in the past, but I believe that we can put them aside and work together for the greater good. I look forward to hearing from you. Sincerely, [Parrot]'" + // } + console.log("View Result", viewResult); +} diff --git a/langchain/.gitignore b/langchain/.gitignore index ca07764e707e..274a6c7ba178 100644 --- a/langchain/.gitignore +++ b/langchain/.gitignore @@ -67,6 +67,9 @@ tools/sql.d.ts tools/webbrowser.cjs tools/webbrowser.js tools/webbrowser.d.ts +tools/gmail.cjs +tools/gmail.js +tools/gmail.d.ts tools/google_calendar.cjs tools/google_calendar.js tools/google_calendar.d.ts diff --git a/langchain/package.json b/langchain/package.json index 3d5e56386799..24c889ed7da0 100644 --- a/langchain/package.json +++ b/langchain/package.json @@ -79,6 +79,9 @@ "tools/webbrowser.cjs", "tools/webbrowser.js", "tools/webbrowser.d.ts", + "tools/gmail.cjs", + "tools/gmail.js", + "tools/gmail.d.ts", "tools/google_calendar.cjs", "tools/google_calendar.js", "tools/google_calendar.d.ts", @@ -1571,6 +1574,11 @@ "import": "./tools/webbrowser.js", "require": "./tools/webbrowser.cjs" }, + "./tools/gmail": { + "types": "./tools/gmail.d.ts", + "import": "./tools/gmail.js", + "require": "./tools/gmail.cjs" + }, "./tools/google_calendar": { "types": "./tools/google_calendar.d.ts", "import": "./tools/google_calendar.js", diff --git a/langchain/scripts/create-entrypoints.js b/langchain/scripts/create-entrypoints.js index a8cf7cfaa321..0c5b071b67e9 100644 --- a/langchain/scripts/create-entrypoints.js +++ b/langchain/scripts/create-entrypoints.js @@ -17,7 +17,8 @@ const entrypoints = { "agents/toolkits/aws_sfn": "agents/toolkits/aws_sfn", "agents/toolkits/sql": "agents/toolkits/sql/index", "agents/format_scratchpad": "agents/format_scratchpad/openai_functions", - "agents/format_scratchpad/openai_tools": "agents/format_scratchpad/openai_tools", + "agents/format_scratchpad/openai_tools": + "agents/format_scratchpad/openai_tools", "agents/format_scratchpad/log": "agents/format_scratchpad/log", "agents/format_scratchpad/xml": "agents/format_scratchpad/xml", "agents/format_scratchpad/log_to_message": @@ -35,6 +36,7 @@ const entrypoints = { "tools/render": "tools/render", "tools/sql": "tools/sql", "tools/webbrowser": "tools/webbrowser", + "tools/gmail": "tools/gmail/index", "tools/google_calendar": "tools/google_calendar/index", "tools/google_places": "tools/google_places", // chains @@ -324,7 +326,7 @@ const entrypoints = { // evaluation evaluation: "evaluation/index", // runnables - "runnables": "runnables/index", + runnables: "runnables/index", "runnables/remote": "runnables/remote", }; @@ -352,6 +354,7 @@ const requiresOptionalDependency = [ "tools/sql", "tools/webbrowser", "tools/google_calendar", + "tools/gmail", "callbacks/handlers/llmonitor", "chains/load", "chains/sql_db", diff --git a/langchain/src/load/import_constants.ts b/langchain/src/load/import_constants.ts index 9bf8f2b4cb46..aecb48a2bde2 100644 --- a/langchain/src/load/import_constants.ts +++ b/langchain/src/load/import_constants.ts @@ -9,6 +9,7 @@ export const optionalImportEntrypoints = [ "langchain/tools/calculator", "langchain/tools/sql", "langchain/tools/webbrowser", + "langchain/tools/gmail", "langchain/tools/google_calendar", "langchain/chains/load", "langchain/chains/query_constructor", diff --git a/langchain/src/load/import_type.d.ts b/langchain/src/load/import_type.d.ts index 052c975320f8..58f83e6a085b 100644 --- a/langchain/src/load/import_type.d.ts +++ b/langchain/src/load/import_type.d.ts @@ -25,6 +25,9 @@ export interface OptionalImportMap { "langchain/tools/webbrowser"?: | typeof import("../tools/webbrowser.js") | Promise; + "langchain/tools/gmail"?: + | typeof import("../tools/gmail/index.js") + | Promise; "langchain/tools/google_calendar"?: | typeof import("../tools/google_calendar/index.js") | Promise; diff --git a/langchain/src/tools/gmail/base.ts b/langchain/src/tools/gmail/base.ts new file mode 100644 index 000000000000..7977f53387d3 --- /dev/null +++ b/langchain/src/tools/gmail/base.ts @@ -0,0 +1,75 @@ +import { gmail_v1, google } from "googleapis"; +import { z } from "zod"; +import { StructuredTool } from "../base.js"; +import { getEnvironmentVariable } from "../../util/env.js"; + +export interface GmailBaseToolParams { + credentials?: { + clientEmail?: string; + privateKey?: string; + keyfile?: string; + }; + scopes?: string[]; +} + +export abstract class GmailBaseTool extends StructuredTool { + private CredentialsSchema = z + .object({ + clientEmail: z + .string() + .min(1) + .default(getEnvironmentVariable("GMAIL_CLIENT_EMAIL") ?? ""), + privateKey: z + .string() + .default(getEnvironmentVariable("GMAIL_PRIVATE_KEY") ?? ""), + keyfile: z + .string() + .default(getEnvironmentVariable("GMAIL_KEYFILE") ?? ""), + }) + .refine( + (credentials) => + credentials.privateKey !== "" || credentials.keyfile !== "", + { + message: + "Missing GMAIL_PRIVATE_KEY or GMAIL_KEYFILE to interact with Gmail", + } + ); + + private GmailBaseToolParamsSchema = z + .object({ + credentials: this.CredentialsSchema.default({}), + scopes: z.array(z.string()).default(["https://mail.google.com/"]), + }) + .default({}); + + name = "Gmail"; + + description = "A tool to send and view emails through Gmail"; + + protected gmail: gmail_v1.Gmail; + + constructor(fields?: Partial) { + super(...arguments); + + const { credentials, scopes } = + this.GmailBaseToolParamsSchema.parse(fields); + + this.gmail = this.getGmail( + scopes, + credentials.clientEmail, + credentials.privateKey, + credentials.keyfile + ); + } + + private getGmail( + scopes: string[], + email: string, + key?: string, + keyfile?: string + ) { + const auth = new google.auth.JWT(email, keyfile, key, scopes); + + return google.gmail({ version: "v1", auth }); + } +} diff --git a/langchain/src/tools/gmail/create_draft.ts b/langchain/src/tools/gmail/create_draft.ts new file mode 100644 index 000000000000..b2d4b56e89c3 --- /dev/null +++ b/langchain/src/tools/gmail/create_draft.ts @@ -0,0 +1,74 @@ +import { z } from "zod"; +import { GmailBaseTool, GmailBaseToolParams } from "./base.js"; +import { CREATE_DRAFT_DESCRIPTION } from "./descriptions.js"; + +export class GmailCreateDraft extends GmailBaseTool { + name = "create_gmail_draft"; + + schema = z.object({ + message: z.string(), + to: z.array(z.string()), + subject: z.string(), + cc: z.array(z.string()).optional(), + bcc: z.array(z.string()).optional(), + }); + + description = CREATE_DRAFT_DESCRIPTION; + + constructor(fields?: GmailBaseToolParams) { + super(fields); + } + + private prepareDraftMessage( + message: string, + to: string[], + subject: string, + cc?: string[], + bcc?: string[] + ) { + const draftMessage = { + message: { + raw: "", + }, + }; + + const email = [ + `To: ${to.join(", ")}`, + `Subject: ${subject}`, + cc ? `Cc: ${cc.join(", ")}` : "", + bcc ? `Bcc: ${bcc.join(", ")}` : "", + "", + message, + ].join("\n"); + + draftMessage.message.raw = Buffer.from(email).toString("base64url"); + + return draftMessage; + } + + async _call(arg: z.output) { + const { message, to, subject, cc, bcc } = arg; + const create_message = this.prepareDraftMessage( + message, + to, + subject, + cc, + bcc + ); + + const response = await this.gmail.users.drafts.create({ + userId: "me", + requestBody: create_message, + }); + + return `Draft created. Draft Id: ${response.data.id}`; + } +} + +export type CreateDraftSchema = { + message: string; + to: string[]; + subject: string; + cc?: string[]; + bcc?: string[]; +}; diff --git a/langchain/src/tools/gmail/descriptions.ts b/langchain/src/tools/gmail/descriptions.ts new file mode 100644 index 000000000000..15193966b232 --- /dev/null +++ b/langchain/src/tools/gmail/descriptions.ts @@ -0,0 +1,119 @@ +export const CREATE_DRAFT_DESCRIPTION = `A tool for creating draft emails in Gmail. + +INPUT example: +{ + "message": "Hello, this is a test draft", + "to": ["example1@email.com", "example2@email.com"], + "subject": "Test Draft", + "cc": ["cc1@email.com"], + "bcc": ["bcc1@email.com"] +} + +OUTPUT: +The output is a confirmation message with the draft ID. +`; + +export const GET_MESSAGE_DESCRIPTION = `A tool for retrieving a specific email message from Gmail using its message ID. + +INPUT example: +{ + "messageId": "unique_message_id_string" +} + +OUTPUT: +The output includes detailed information about the retrieved email message. This includes the subject, body, sender (from), recipients (to), date of the email, and the message ID. If any of these details are not available in the email, the tool will throw an error indicating the missing information. + +Example Output: +"Result for the prompt unique_message_id_string +{ + 'subject': 'Email Subject', + 'body': 'Email Body Content', + 'from': 'sender@email.com', + 'to': 'recipient@email.com', + 'date': 'Email Date', + 'messageId': 'unique_message_id_string' +}" +`; + +export const GET_THREAD_DESCRIPTION = `A tool for retrieving an entire email thread from Gmail using the thread ID. + +INPUT example: +{ + "threadId": "unique_thread_id_string" +} + +OUTPUT: +The output includes an array of all the messages in the specified thread. Each message in the array contains detailed information including the subject, body, sender (from), recipients (to), date of the email, and the message ID. If any of these details are not available in a message, the tool will throw an error indicating the missing information. + +Example Output: +"Result for the prompt unique_thread_id_string +[ + { + 'subject': 'Email Subject', + 'body': 'Email Body Content', + 'from': 'sender@email.com', + 'to': 'recipient@email.com', + 'date': 'Email Date', + 'messageId': 'unique_message_id_string' + }, + ... (other messages in the thread) +]" +`; + +export const SEND_MESSAGE_DESCRIPTION = `A tool for sending an email message using Gmail. It allows users to specify recipients, subject, and the content of the message, along with optional cc and bcc fields. + +INPUT example: +{ + "message": "Hello, this is a test email", + "to": ["recipient1@email.com", "recipient2@email.com"], + "subject": "Test Email", + "cc": ["cc1@email.com"], + "bcc": ["bcc1@email.com"] +} + +OUTPUT: +The output is a confirmation message with the ID of the sent email. If there is an error during the sending process, the tool will throw an error with a description of the problem. + +Example Output: +"Message sent. Message Id: unique_message_id_string" +`; + +export const SEARCH_DESCRIPTION = `A tool for searching email messages or threads in Gmail using a specific query. It offers the flexibility to choose between messages and threads as the search resource. + +INPUT example: +{ + "query": "specific search query", + "maxResults": 10, // Optional: number of results to return + "resource": "messages" // Optional: can be "messages" or "threads" +} + +OUTPUT: +The output is a JSON list of either email messages or threads, depending on the specified resource, that matches the search query. For 'messages', the output includes details like the message ID, thread ID, snippet, body, subject, and sender of each message. For 'threads', it includes the thread ID, snippet, body, subject, and sender of the first message in each thread. If no data is returned, or if the specified resource is invalid, the tool throws an error with a relevant message. + +Example Output for 'messages': +"Result for the query 'specific search query': +[ + { + 'id': 'message_id', + 'threadId': 'thread_id', + 'snippet': 'message snippet', + 'body': 'message body', + 'subject': 'message subject', + 'sender': 'sender's email' + }, + ... (other messages matching the query) +]" + +Example Output for 'threads': +"Result for the query 'specific search query': +[ + { + 'id': 'thread_id', + 'snippet': 'thread snippet', + 'body': 'first message body', + 'subject': 'first message subject', + 'sender': 'first message sender' + }, + ... (other threads matching the query) +]" +`; diff --git a/langchain/src/tools/gmail/get_message.ts b/langchain/src/tools/gmail/get_message.ts new file mode 100644 index 000000000000..5864c427d7ce --- /dev/null +++ b/langchain/src/tools/gmail/get_message.ts @@ -0,0 +1,95 @@ +import { z } from "zod"; +import { GmailBaseToolParams, GmailBaseTool } from "./base.js"; +import { GET_MESSAGE_DESCRIPTION } from "./descriptions.js"; + +export class GmailGetMessage extends GmailBaseTool { + name = "gmail_get_message"; + + schema = z.object({ + messageId: z.string(), + }); + + description = GET_MESSAGE_DESCRIPTION; + + constructor(fields?: GmailBaseToolParams) { + super(fields); + } + + async _call(arg: z.output) { + const { messageId } = arg; + + const message = await this.gmail.users.messages.get({ + userId: "me", + id: messageId, + }); + + const { data } = message; + + if (!data) { + throw new Error("No data returned from Gmail"); + } + + const { payload } = data; + + if (!payload) { + throw new Error("No payload returned from Gmail"); + } + + const { headers } = payload; + + if (!headers) { + throw new Error("No headers returned from Gmail"); + } + + const subject = headers.find((header) => header.name === "Subject"); + + if (!subject) { + throw new Error("No subject returned from Gmail"); + } + + const body = headers.find((header) => header.name === "Body"); + + if (!body) { + throw new Error("No body returned from Gmail"); + } + + const from = headers.find((header) => header.name === "From"); + + if (!from) { + throw new Error("No from returned from Gmail"); + } + + const to = headers.find((header) => header.name === "To"); + + if (!to) { + throw new Error("No to returned from Gmail"); + } + + const date = headers.find((header) => header.name === "Date"); + + if (!date) { + throw new Error("No date returned from Gmail"); + } + + const messageIdHeader = headers.find( + (header) => header.name === "Message-ID" + ); + + if (!messageIdHeader) { + throw new Error("No message id returned from Gmail"); + } + + return `Result for the prompt ${messageId} \n${JSON.stringify({ + subject: subject.value, + body: body.value, + from: from.value, + to: to.value, + date: date.value, + messageId: messageIdHeader.value, + })}`; + } +} + +export type GetMessageSchema = { + messageId: string; +}; diff --git a/langchain/src/tools/gmail/get_thread.ts b/langchain/src/tools/gmail/get_thread.ts new file mode 100644 index 000000000000..0310bf053d0c --- /dev/null +++ b/langchain/src/tools/gmail/get_thread.ts @@ -0,0 +1,105 @@ +import { z } from "zod"; +import { GmailBaseTool, GmailBaseToolParams } from "./base.js"; +import { GET_THREAD_DESCRIPTION } from "./descriptions.js"; + +export class GmailGetThread extends GmailBaseTool { + name = "gmail_get_thread"; + + schema = z.object({ + threadId: z.string(), + }); + + description = GET_THREAD_DESCRIPTION; + + constructor(fields?: GmailBaseToolParams) { + super(fields); + } + + async _call(arg: z.output) { + const { threadId } = arg; + + const thread = await this.gmail.users.threads.get({ + userId: "me", + id: threadId, + }); + + const { data } = thread; + + if (!data) { + throw new Error("No data returned from Gmail"); + } + + const { messages } = data; + + if (!messages) { + throw new Error("No messages returned from Gmail"); + } + + return `Result for the prompt ${threadId} \n${JSON.stringify( + messages.map((message) => { + const { payload } = message; + + if (!payload) { + throw new Error("No payload returned from Gmail"); + } + + const { headers } = payload; + + if (!headers) { + throw new Error("No headers returned from Gmail"); + } + + const subject = headers.find((header) => header.name === "Subject"); + + if (!subject) { + throw new Error("No subject returned from Gmail"); + } + + const body = headers.find((header) => header.name === "Body"); + + if (!body) { + throw new Error("No body returned from Gmail"); + } + + const from = headers.find((header) => header.name === "From"); + + if (!from) { + throw new Error("No from returned from Gmail"); + } + + const to = headers.find((header) => header.name === "To"); + + if (!to) { + throw new Error("No to returned from Gmail"); + } + + const date = headers.find((header) => header.name === "Date"); + + if (!date) { + throw new Error("No date returned from Gmail"); + } + + const messageIdHeader = headers.find( + (header) => header.name === "Message-ID" + ); + + if (!messageIdHeader) { + throw new Error("No message id returned from Gmail"); + } + + return { + subject: subject.value, + body: body.value, + from: from.value, + to: to.value, + date: date.value, + messageId: messageIdHeader.value, + }; + }) + )}`; + } +} + +export type GetThreadSchema = { + threadId: string; +}; diff --git a/langchain/src/tools/gmail/index.ts b/langchain/src/tools/gmail/index.ts new file mode 100644 index 000000000000..d2f854da54a4 --- /dev/null +++ b/langchain/src/tools/gmail/index.ts @@ -0,0 +1,12 @@ +export { GmailCreateDraft } from "./create_draft.js"; +export { GmailGetMessage } from "./get_message.js"; +export { GmailGetThread } from "./get_thread.js"; +export { GmailSearch } from "./search.js"; +export { GmailSendMessage } from "./send_message.js"; + +export type { GmailBaseToolParams } from "./base.js"; +export type { CreateDraftSchema } from "./create_draft.js"; +export type { GetMessageSchema } from "./get_message.js"; +export type { GetThreadSchema } from "./get_thread.js"; +export type { SearchSchema } from "./search.js"; +export type { SendMessageSchema } from "./send_message.js"; diff --git a/langchain/src/tools/gmail/search.ts b/langchain/src/tools/gmail/search.ts new file mode 100644 index 000000000000..9957a11c8c3b --- /dev/null +++ b/langchain/src/tools/gmail/search.ts @@ -0,0 +1,135 @@ +import { gmail_v1 } from "googleapis"; +import { z } from "zod"; +import { GmailBaseTool, GmailBaseToolParams } from "./base.js"; +import { SEARCH_DESCRIPTION } from "./descriptions.js"; + +export class GmailSearch extends GmailBaseTool { + name = "search_gmail"; + + schema = z.object({ + query: z.string(), + maxResults: z.number().optional(), + resource: z.enum(["messages", "threads"]).optional(), + }); + + description = SEARCH_DESCRIPTION; + + constructor(fields?: GmailBaseToolParams) { + super(fields); + } + + async _call(arg: z.output) { + const { query, maxResults = 10, resource = "messages" } = arg; + + const response = await this.gmail.users.messages.list({ + userId: "me", + q: query, + maxResults, + }); + + const { data } = response; + + if (!data) { + throw new Error("No data returned from Gmail"); + } + + const { messages } = data; + + if (!messages) { + throw new Error("No messages returned from Gmail"); + } + + if (resource === "messages") { + const parsedMessages = await this.parseMessages(messages); + return `Result for the query ${query}:\n${JSON.stringify( + parsedMessages + )}`; + } else if (resource === "threads") { + const parsedThreads = await this.parseThreads(messages); + return `Result for the query ${query}:\n${JSON.stringify(parsedThreads)}`; + } + + throw new Error(`Invalid resource: ${resource}`); + } + + async parseMessages( + messages: gmail_v1.Schema$Message[] + ): Promise { + const parsedMessages = await Promise.all( + messages.map(async (message) => { + const messageData = await this.gmail.users.messages.get({ + userId: "me", + format: "raw", + id: message.id ?? "", + }); + + const headers = messageData.data.payload?.headers || []; + + const subject = headers.find((header) => header.name === "Subject"); + const sender = headers.find((header) => header.name === "From"); + + let body = ""; + if (messageData.data.payload?.parts) { + body = messageData.data.payload.parts + .map((part) => part.body?.data ?? "") + .join(""); + } else if (messageData.data.payload?.body?.data) { + body = messageData.data.payload.body.data; + } + + return { + id: message.id, + threadId: message.threadId, + snippet: message.snippet, + body, + subject, + sender, + }; + }) + ); + return parsedMessages; + } + + async parseThreads( + threads: gmail_v1.Schema$Thread[] + ): Promise { + const parsedThreads = await Promise.all( + threads.map(async (thread) => { + const threadData = await this.gmail.users.threads.get({ + userId: "me", + format: "raw", + id: thread.id ?? "", + }); + + const headers = threadData.data.messages?.[0]?.payload?.headers || []; + + const subject = headers.find((header) => header.name === "Subject"); + const sender = headers.find((header) => header.name === "From"); + + let body = ""; + if (threadData.data.messages?.[0]?.payload?.parts) { + body = threadData.data.messages[0].payload.parts + .map((part) => part.body?.data ?? "") + .join(""); + } else if (threadData.data.messages?.[0]?.payload?.body?.data) { + body = threadData.data.messages[0].payload.body.data; + } + + return { + id: thread.id, + snippet: thread.snippet, + body, + subject, + sender, + }; + }) + ); + return parsedThreads; + } +} + +export type SearchSchema = { + query: string; + maxResults?: number; + resource?: "messages" | "threads"; +}; diff --git a/langchain/src/tools/gmail/send_message.ts b/langchain/src/tools/gmail/send_message.ts new file mode 100644 index 000000000000..b995dfbff0b2 --- /dev/null +++ b/langchain/src/tools/gmail/send_message.ts @@ -0,0 +1,84 @@ +import { z } from "zod"; +import { GmailBaseTool, GmailBaseToolParams } from "./base.js"; +import { GET_MESSAGE_DESCRIPTION } from "./descriptions.js"; + +export class GmailSendMessage extends GmailBaseTool { + name = "gmail_send_message"; + + schema = z.object({ + message: z.string(), + to: z.array(z.string()), + subject: z.string(), + cc: z.array(z.string()).optional(), + bcc: z.array(z.string()).optional(), + }); + + description = GET_MESSAGE_DESCRIPTION; + + constructor(fields?: GmailBaseToolParams) { + super(fields); + } + + private createEmailMessage({ + message, + to, + subject, + cc, + bcc, + }: z.infer): string { + const emailLines: string[] = []; + + // Format the recipient(s) + const formatEmailList = (emails: string | string[]): string => + Array.isArray(emails) ? emails.join(",") : emails; + + emailLines.push(`To: ${formatEmailList(to)}`); + if (cc) emailLines.push(`Cc: ${formatEmailList(cc)}`); + if (bcc) emailLines.push(`Bcc: ${formatEmailList(bcc)}`); + emailLines.push(`Subject: ${subject}`); + emailLines.push(""); + emailLines.push(message); + + // Convert the email message to base64url string + const email = emailLines.join("\r\n").trim(); + // this encode may be an issue + return Buffer.from(email).toString("base64url"); + } + + async _call({ + message, + to, + subject, + cc, + bcc, + }: z.output): Promise { + const rawMessage = this.createEmailMessage({ + message, + to, + subject, + cc, + bcc, + }); + + try { + const response = await this.gmail.users.messages.send({ + userId: "me", + requestBody: { + raw: rawMessage, + }, + }); + + return `Message sent. Message Id: ${response.data.id}`; + } catch (error) { + throw new Error(`An error occurred while sending the message: ${error}`); + } + } +} + +export type SendMessageSchema = { + message: string; + to: string[]; + subject: string; + cc?: string[]; + bcc?: string[]; +}; diff --git a/langchain/src/tools/tests/gmail.test.ts b/langchain/src/tools/tests/gmail.test.ts new file mode 100644 index 000000000000..e44b6f7fef36 --- /dev/null +++ b/langchain/src/tools/tests/gmail.test.ts @@ -0,0 +1,63 @@ +import { jest, expect, describe } from "@jest/globals"; +import { GmailGetMessage } from "../gmail/get_message.js"; + +jest.mock("googleapis", () => ({ + google: { + auth: { + JWT: jest.fn().mockImplementation(() => ({})), + }, + }, +})); + +describe("GmailBaseTool using GmailGetMessage", () => { + it("should be setup with correct parameters", async () => { + const params = { + credentials: { + clientEmail: "test@email.com", + privateKey: "privateKey", + }, + scopes: ["gmail_scope1"], + }; + const instance = new GmailGetMessage(params); + expect(instance.name).toBe("gmail_get_message"); + }); + + it("should throw an error if both privateKey and keyfile are missing", async () => { + const params = { + credentials: {}, + scopes: ["gmail_scope1"], + }; + + expect(() => new GmailGetMessage(params)).toThrow(); + }); + + it("should throw error with only client_email", async () => { + const params = { + credentials: { + clientEmail: "client_email", + }, + }; + + expect(() => new GmailGetMessage(params)).toThrow(); + }); + + it("should throw error with only private_key", async () => { + const params = { + credentials: { + privateKey: "privateKey", + }, + }; + + expect(() => new GmailGetMessage(params)).toThrow(); + }); + + it("should throw error with only keyfile", async () => { + const params = { + credentials: { + keyfile: "keyfile", + }, + }; + + expect(() => new GmailGetMessage(params)).toThrow(); + }); +}); From 26f1ecaa72f15e4556d5c53dcff0ddc668248f03 Mon Sep 17 00:00:00 2001 From: Eze Date: Tue, 5 Dec 2023 00:31:57 -0300 Subject: [PATCH 32/44] feat: Add ObsidianLoader to Document loaders (#3494) * Add ObsidianLoader integration * Fix Notion test not to consider Obsidian '.md' files * Fix lint --------- Co-authored-by: jacoblee93 --- docs/api_refs/typedoc.json | 1 + .../example_data/obsidian/bad_frontmatter.md | 9 + .../example_data/obsidian/frontmatter.md | 5 + .../example_data/obsidian/no_frontmatter.md | 13 + .../example_data/obsidian/no_metadata.md | 1 + .../obsidian/tags_and_frontmatter.md | 35 +++ examples/src/document_loaders/obsidian.ts | 11 + langchain/.gitignore | 3 + langchain/package.json | 8 + langchain/scripts/create-entrypoints.js | 2 + langchain/src/document_loaders/fs/obsidian.ts | 264 ++++++++++++++++++ .../tests/example_data/{ => notion}/notion.md | 0 .../example_data/obsidian/bad_frontmatter.md | 9 + .../example_data/obsidian/frontmatter.md | 5 + .../example_data/obsidian/no_frontmatter.md | 14 + .../example_data/obsidian/no_metadata.md | 1 + .../obsidian/tags_and_frontmatter.md | 36 +++ .../src/document_loaders/tests/notion.test.ts | 2 +- .../document_loaders/tests/obsidian.test.ts | 191 +++++++++++++ langchain/src/load/import_constants.ts | 1 + langchain/src/load/import_type.d.ts | 3 + 21 files changed, 613 insertions(+), 1 deletion(-) create mode 100644 examples/src/document_loaders/example_data/obsidian/bad_frontmatter.md create mode 100644 examples/src/document_loaders/example_data/obsidian/frontmatter.md create mode 100644 examples/src/document_loaders/example_data/obsidian/no_frontmatter.md create mode 100644 examples/src/document_loaders/example_data/obsidian/no_metadata.md create mode 100644 examples/src/document_loaders/example_data/obsidian/tags_and_frontmatter.md create mode 100644 examples/src/document_loaders/obsidian.ts create mode 100644 langchain/src/document_loaders/fs/obsidian.ts rename langchain/src/document_loaders/tests/example_data/{ => notion}/notion.md (100%) create mode 100644 langchain/src/document_loaders/tests/example_data/obsidian/bad_frontmatter.md create mode 100644 langchain/src/document_loaders/tests/example_data/obsidian/frontmatter.md create mode 100644 langchain/src/document_loaders/tests/example_data/obsidian/no_frontmatter.md create mode 100644 langchain/src/document_loaders/tests/example_data/obsidian/no_metadata.md create mode 100644 langchain/src/document_loaders/tests/example_data/obsidian/tags_and_frontmatter.md create mode 100644 langchain/src/document_loaders/tests/obsidian.test.ts diff --git a/docs/api_refs/typedoc.json b/docs/api_refs/typedoc.json index a7f8c8b24293..6f28212aacb8 100644 --- a/docs/api_refs/typedoc.json +++ b/docs/api_refs/typedoc.json @@ -176,6 +176,7 @@ "./langchain/src/document_loaders/fs/epub.ts", "./langchain/src/document_loaders/fs/csv.ts", "./langchain/src/document_loaders/fs/notion.ts", + "./langchain/src/document_loaders/fs/obsidian.ts", "./langchain/src/document_loaders/fs/unstructured.ts", "./langchain/src/document_loaders/fs/openai_whisper_audio.ts", "./langchain/src/document_loaders/fs/pptx.ts", diff --git a/examples/src/document_loaders/example_data/obsidian/bad_frontmatter.md b/examples/src/document_loaders/example_data/obsidian/bad_frontmatter.md new file mode 100644 index 000000000000..57698653173d --- /dev/null +++ b/examples/src/document_loaders/example_data/obsidian/bad_frontmatter.md @@ -0,0 +1,9 @@ +--- +anArray: + one +- two +- three +tags: 'onetag', 'twotag' ] +--- + +A document with frontmatter that isn't valid. \ No newline at end of file diff --git a/examples/src/document_loaders/example_data/obsidian/frontmatter.md b/examples/src/document_loaders/example_data/obsidian/frontmatter.md new file mode 100644 index 000000000000..80396d268f94 --- /dev/null +++ b/examples/src/document_loaders/example_data/obsidian/frontmatter.md @@ -0,0 +1,5 @@ +--- +tags: journal/entry, obsidian +--- + +No other content than the frontmatter. \ No newline at end of file diff --git a/examples/src/document_loaders/example_data/obsidian/no_frontmatter.md b/examples/src/document_loaders/example_data/obsidian/no_frontmatter.md new file mode 100644 index 000000000000..74c2405506e2 --- /dev/null +++ b/examples/src/document_loaders/example_data/obsidian/no_frontmatter.md @@ -0,0 +1,13 @@ +### Description +#recipes #dessert #cookies + +A document with HR elements that might trip up a front matter parser: + +--- + +### Ingredients + +- 3/4 cup (170g) **unsalted butter**, slightly softened to room temperature. +- 1 and 1/2 cups (180g) **confectioners’ sugar** + +--- \ No newline at end of file diff --git a/examples/src/document_loaders/example_data/obsidian/no_metadata.md b/examples/src/document_loaders/example_data/obsidian/no_metadata.md new file mode 100644 index 000000000000..991d076e28da --- /dev/null +++ b/examples/src/document_loaders/example_data/obsidian/no_metadata.md @@ -0,0 +1 @@ +A markdown document with no additional metadata. \ No newline at end of file diff --git a/examples/src/document_loaders/example_data/obsidian/tags_and_frontmatter.md b/examples/src/document_loaders/example_data/obsidian/tags_and_frontmatter.md new file mode 100644 index 000000000000..cb373d396806 --- /dev/null +++ b/examples/src/document_loaders/example_data/obsidian/tags_and_frontmatter.md @@ -0,0 +1,35 @@ +--- +aFloat: 13.12345 +anInt: 15 +aBool: true +aString: string value +anArray: +- one +- two +- three +aDict: + dictId1: '58417' + dictId2: 1500 +tags: [ 'onetag', 'twotag' ] +--- + +# Tags + + ()#notatag +#12345 + #read +something #tagWithCases +- #tag-with-dash +#tag_with_underscore #tag/with/nesting + +# Dataview + +Here is some data in a [dataview1:: a value] line. +Here is even more data in a (dataview2:: another value) line. +dataview3:: more data +notdataview4: this is not a field +notdataview5: this is not a field + +# Text content + +https://example.com/blog/#not-a-tag \ No newline at end of file diff --git a/examples/src/document_loaders/obsidian.ts b/examples/src/document_loaders/obsidian.ts new file mode 100644 index 000000000000..279618b0d75d --- /dev/null +++ b/examples/src/document_loaders/obsidian.ts @@ -0,0 +1,11 @@ +import { ObsidianLoader } from "langchain/document_loaders/fs/obsidian"; + +export const run = async () => { + const loader = new ObsidianLoader( + "src/document_loaders/example_data/obsidian" + ); + + const docs = await loader.load(); + + console.log({ docs }); +}; diff --git a/langchain/.gitignore b/langchain/.gitignore index 274a6c7ba178..e45de564ddd8 100644 --- a/langchain/.gitignore +++ b/langchain/.gitignore @@ -472,6 +472,9 @@ document_loaders/fs/csv.d.ts document_loaders/fs/notion.cjs document_loaders/fs/notion.js document_loaders/fs/notion.d.ts +document_loaders/fs/obsidian.cjs +document_loaders/fs/obsidian.js +document_loaders/fs/obsidian.d.ts document_loaders/fs/unstructured.cjs document_loaders/fs/unstructured.js document_loaders/fs/unstructured.d.ts diff --git a/langchain/package.json b/langchain/package.json index 24c889ed7da0..984287ff1edc 100644 --- a/langchain/package.json +++ b/langchain/package.json @@ -484,6 +484,9 @@ "document_loaders/fs/notion.cjs", "document_loaders/fs/notion.js", "document_loaders/fs/notion.d.ts", + "document_loaders/fs/obsidian.cjs", + "document_loaders/fs/obsidian.js", + "document_loaders/fs/obsidian.d.ts", "document_loaders/fs/unstructured.cjs", "document_loaders/fs/unstructured.js", "document_loaders/fs/unstructured.d.ts", @@ -2249,6 +2252,11 @@ "import": "./document_loaders/fs/notion.js", "require": "./document_loaders/fs/notion.cjs" }, + "./document_loaders/fs/obsidian": { + "types": "./document_loaders/fs/obsidian.d.ts", + "import": "./document_loaders/fs/obsidian.js", + "require": "./document_loaders/fs/obsidian.cjs" + }, "./document_loaders/fs/unstructured": { "types": "./document_loaders/fs/unstructured.d.ts", "import": "./document_loaders/fs/unstructured.js", diff --git a/langchain/scripts/create-entrypoints.js b/langchain/scripts/create-entrypoints.js index 0c5b071b67e9..69ac7bfd5740 100644 --- a/langchain/scripts/create-entrypoints.js +++ b/langchain/scripts/create-entrypoints.js @@ -184,6 +184,7 @@ const entrypoints = { "document_loaders/fs/epub": "document_loaders/fs/epub", "document_loaders/fs/csv": "document_loaders/fs/csv", "document_loaders/fs/notion": "document_loaders/fs/notion", + "document_loaders/fs/obsidian": "document_loaders/fs/obsidian", "document_loaders/fs/unstructured": "document_loaders/fs/unstructured", "document_loaders/fs/openai_whisper_audio": "document_loaders/fs/openai_whisper_audio", @@ -454,6 +455,7 @@ const requiresOptionalDependency = [ "document_loaders/fs/epub", "document_loaders/fs/csv", "document_loaders/fs/notion", + "document_loaders/fs/obsidian", "document_loaders/fs/unstructured", "document_loaders/fs/openai_whisper_audio", "document_loaders/fs/pptx", diff --git a/langchain/src/document_loaders/fs/obsidian.ts b/langchain/src/document_loaders/fs/obsidian.ts new file mode 100644 index 000000000000..a2770cc7f877 --- /dev/null +++ b/langchain/src/document_loaders/fs/obsidian.ts @@ -0,0 +1,264 @@ +import type { basename as BasenameT } from "node:path"; +import type { readFile as ReadFileT, stat as StatT } from "node:fs/promises"; +import yaml from "js-yaml"; +import { getEnv } from "../../util/env.js"; +import { DirectoryLoader, UnknownHandling } from "./directory.js"; +import { BaseDocumentLoader } from "../base.js"; +import { Document } from "../../document.js"; + +export type FrontMatter = { + title?: string; + description?: string; + tags?: string[] | string; + [key: string]: unknown; +}; + +export interface ObsidianFileLoaderOptions { + encoding?: BufferEncoding; + collectMetadata?: boolean; +} + +/** + * Represents a loader for Obsidian markdown files. This loader extends the BaseDocumentLoader + * and provides functionality to parse and extract metadata, tags, and dataview fields from + * Obsidian markdown files. + */ +class ObsidianFileLoader extends BaseDocumentLoader { + private filePath: string; + + private encoding: BufferEncoding; + + private collectMetadata: boolean; + + /** + * Initializes a new instance of the ObsidianFileLoader class. + * @param filePath The path to the Obsidian markdown file. + * @param encoding The character encoding to use when reading the file. Defaults to 'utf-8'. + * @param collectMetadata Determines whether metadata should be collected from the file. Defaults to true. + */ + constructor( + filePath: string, + { + encoding = "utf-8", + collectMetadata = true, + }: ObsidianFileLoaderOptions = {} + ) { + super(); + this.filePath = filePath; + this.encoding = encoding; + this.collectMetadata = collectMetadata; + } + + private static FRONT_MATTER_REGEX = /^---\n(.*?)\n---\n/s; + + /** + * Parses the YAML front matter from the given content string. + * @param content The string content of the markdown file. + * @returns An object representing the parsed front matter. + */ + private parseFrontMatter(content: string): FrontMatter { + if (!this.collectMetadata) { + return {}; + } + + const match = content.match(ObsidianFileLoader.FRONT_MATTER_REGEX); + if (!match) { + return {}; + } + + try { + const frontMatter = yaml.load(match[1]) as FrontMatter; + if (frontMatter.tags && typeof frontMatter.tags === "string") { + frontMatter.tags = frontMatter.tags.split(", "); + } + + return frontMatter; + } catch (e) { + console.warn("Encountered non-yaml frontmatter"); + return {}; + } + } + + /** + * Removes YAML front matter from the given content string. + * @param content The string content of the markdown file. + * @returns The content string with the front matter removed. + */ + private removeFrontMatter(content: string): string { + if (!this.collectMetadata) { + return content; + } + + return content.replace(ObsidianFileLoader.FRONT_MATTER_REGEX, ""); + } + + private static TAG_REGEX = /(?:\s|^)#([a-zA-Z_][\w/-]*)/g; + + /** + * Parses Obsidian-style tags from the given content string. + * @param content The string content of the markdown file. + * @returns A set of parsed tags. + */ + private parseObsidianTags(content: string): Set { + if (!this.collectMetadata) { + return new Set(); + } + + const matches = content.matchAll(ObsidianFileLoader.TAG_REGEX); + const tags = new Set(); + for (const match of matches) { + tags.add(match[1]); + } + + return tags; + } + + private static DATAVIEW_LINE_REGEX = /^\s*(\w+)::\s*(.*)$/gm; + + private static DATAVIEW_INLINE_BRACKET_REGEX = /\[(\w+)::\s*(.*)\]/gm; + + private static DATAVIEW_INLINE_PAREN_REGEX = /\((\w+)::\s*(.*)\)/gm; + + /** + * Parses dataview fields from the given content string. + * @param content The string content of the markdown file. + * @returns A record object containing key-value pairs of dataview fields. + */ + private parseObsidianDataviewFields(content: string): Record { + if (!this.collectMetadata) { + return {}; + } + + const fields: Record = {}; + const lineMatches = content.matchAll( + ObsidianFileLoader.DATAVIEW_LINE_REGEX + ); + for (const [, key, value] of lineMatches) { + fields[key] = value; + } + + const bracketMatches = content.matchAll( + ObsidianFileLoader.DATAVIEW_INLINE_BRACKET_REGEX + ); + for (const [, key, value] of bracketMatches) { + fields[key] = value; + } + + const parenMatches = content.matchAll( + ObsidianFileLoader.DATAVIEW_INLINE_PAREN_REGEX + ); + for (const [, key, value] of parenMatches) { + fields[key] = value; + } + + return fields; + } + + /** + * Converts metadata to a format compatible with Langchain. + * @param metadata The metadata object to convert. + * @returns A record object containing key-value pairs of Langchain-compatible metadata. + */ + private toLangchainCompatibleMetadata(metadata: Record) { + const result: Record = {}; + for (const [key, value] of Object.entries(metadata)) { + if (typeof value === "string" || typeof value === "number") { + result[key] = value; + } else { + result[key] = JSON.stringify(value); + } + } + return result; + } + + /** + * It loads the Obsidian file, parses it, and returns a `Document` instance. + * @returns An array of `Document` instances to comply with the BaseDocumentLoader interface. + */ + public async load(): Promise { + const documents: Document[] = []; + + const { basename, readFile, stat } = await ObsidianFileLoader.imports(); + const fileName = basename(this.filePath); + const stats = await stat(this.filePath); + let content = await readFile(this.filePath, this.encoding); + + const frontMatter = this.parseFrontMatter(content); + const tags = this.parseObsidianTags(content); + const dataviewFields = this.parseObsidianDataviewFields(content); + content = this.removeFrontMatter(content); + + const metadata: Document["metadata"] = { + source: fileName, + path: this.filePath, + created: stats.birthtimeMs, + lastModified: stats.mtimeMs, + lastAccessed: stats.atimeMs, + ...this.toLangchainCompatibleMetadata(frontMatter), + ...dataviewFields, + }; + + if (tags.size || frontMatter.tags) { + metadata.tags = Array.from( + new Set([...tags, ...(frontMatter.tags ?? [])]) + ).join(","); + } + + documents.push( + new Document({ + pageContent: content, + metadata, + }) + ); + + return documents; + } + + /** + * Imports the necessary functions from the `node:path` and + * `node:fs/promises` modules. It is used to dynamically import the + * functions when needed. If the import fails, it throws an error + * indicating that the modules failed to load. + * @returns A promise that resolves to an object containing the imported functions. + */ + static async imports(): Promise<{ + basename: typeof BasenameT; + readFile: typeof ReadFileT; + stat: typeof StatT; + }> { + try { + const { basename } = await import("node:path"); + const { readFile, stat } = await import("node:fs/promises"); + return { basename, readFile, stat }; + } catch (e) { + console.error(e); + throw new Error( + `Failed to load fs/promises. ObsidianFileLoader available only on environment 'node'. It appears you are running environment '${getEnv()}'. See https:// for alternatives.` + ); + } + } +} + +/** + * Represents a loader for directories containing Obsidian markdown files. This loader extends + * the DirectoryLoader and provides functionality to load and parse '.md' files with YAML frontmatter, + * Obsidian tags, and Dataview fields. + */ +export class ObsidianLoader extends DirectoryLoader { + /** + * Initializes a new instance of the ObsidianLoader class. + * @param directoryPath The path to the directory containing Obsidian markdown files. + * @param encoding The character encoding to use when reading files. Defaults to 'utf-8'. + * @param collectMetadata Determines whether metadata should be collected from the files. Defaults to true. + */ + constructor(directoryPath: string, options?: ObsidianFileLoaderOptions) { + super( + directoryPath, + { + ".md": (filePath) => new ObsidianFileLoader(filePath, options), + }, + true, + UnknownHandling.Ignore + ); + } +} diff --git a/langchain/src/document_loaders/tests/example_data/notion.md b/langchain/src/document_loaders/tests/example_data/notion/notion.md similarity index 100% rename from langchain/src/document_loaders/tests/example_data/notion.md rename to langchain/src/document_loaders/tests/example_data/notion/notion.md diff --git a/langchain/src/document_loaders/tests/example_data/obsidian/bad_frontmatter.md b/langchain/src/document_loaders/tests/example_data/obsidian/bad_frontmatter.md new file mode 100644 index 000000000000..edc335e195ff --- /dev/null +++ b/langchain/src/document_loaders/tests/example_data/obsidian/bad_frontmatter.md @@ -0,0 +1,9 @@ +--- +anArray: + one +- two +- three +tags: 'onetag', 'twotag' ] +--- + +A document with frontmatter that isn't valid. diff --git a/langchain/src/document_loaders/tests/example_data/obsidian/frontmatter.md b/langchain/src/document_loaders/tests/example_data/obsidian/frontmatter.md new file mode 100644 index 000000000000..bb1f5b9f0fc5 --- /dev/null +++ b/langchain/src/document_loaders/tests/example_data/obsidian/frontmatter.md @@ -0,0 +1,5 @@ +--- +tags: journal/entry, obsidian +--- + +No other content than the frontmatter. diff --git a/langchain/src/document_loaders/tests/example_data/obsidian/no_frontmatter.md b/langchain/src/document_loaders/tests/example_data/obsidian/no_frontmatter.md new file mode 100644 index 000000000000..3943ec888c7e --- /dev/null +++ b/langchain/src/document_loaders/tests/example_data/obsidian/no_frontmatter.md @@ -0,0 +1,14 @@ +### Description + +#recipes #dessert #cookies + +A document with HR elements that might trip up a front matter parser: + +--- + +### Ingredients + +- 3/4 cup (170g) **unsalted butter**, slightly softened to room temperature. +- 1 and 1/2 cups (180g) **confectioners’ sugar** + +--- diff --git a/langchain/src/document_loaders/tests/example_data/obsidian/no_metadata.md b/langchain/src/document_loaders/tests/example_data/obsidian/no_metadata.md new file mode 100644 index 000000000000..70258e5aea71 --- /dev/null +++ b/langchain/src/document_loaders/tests/example_data/obsidian/no_metadata.md @@ -0,0 +1 @@ +A markdown document with no additional metadata. diff --git a/langchain/src/document_loaders/tests/example_data/obsidian/tags_and_frontmatter.md b/langchain/src/document_loaders/tests/example_data/obsidian/tags_and_frontmatter.md new file mode 100644 index 000000000000..1d108a11ac87 --- /dev/null +++ b/langchain/src/document_loaders/tests/example_data/obsidian/tags_and_frontmatter.md @@ -0,0 +1,36 @@ +--- +aFloat: 13.12345 +anInt: 15 +aBool: true +aString: string value +anArray: + - one + - two + - three +aDict: + dictId1: "58417" + dictId2: 1500 +tags: ["onetag", "twotag"] +--- + +# Tags + +()#notatag +#12345 +#read +something #tagWithCases + +- #tag-with-dash + #tag_with_underscore #tag/with/nesting + +# Dataview + +Here is some data in a [dataview1:: a value] line. +Here is even more data in a (dataview2:: another value) line. +dataview3:: more data +notdataview4: this is not a field +notdataview5: this is not a field + +# Text content + +https://example.com/blog/#not-a-tag diff --git a/langchain/src/document_loaders/tests/notion.test.ts b/langchain/src/document_loaders/tests/notion.test.ts index 025da7591f59..ca0680685023 100644 --- a/langchain/src/document_loaders/tests/notion.test.ts +++ b/langchain/src/document_loaders/tests/notion.test.ts @@ -6,7 +6,7 @@ import { NotionLoader } from "../fs/notion.js"; test("Test Notion Loader", async () => { const directoryPath = path.resolve( path.dirname(url.fileURLToPath(import.meta.url)), - "./example_data" + "./example_data/notion" ); const loader = new NotionLoader(directoryPath); const docs = await loader.load(); diff --git a/langchain/src/document_loaders/tests/obsidian.test.ts b/langchain/src/document_loaders/tests/obsidian.test.ts new file mode 100644 index 000000000000..0b0fcd8f906f --- /dev/null +++ b/langchain/src/document_loaders/tests/obsidian.test.ts @@ -0,0 +1,191 @@ +import { test, expect } from "@jest/globals"; +import * as url from "node:url"; +import * as path from "node:path"; +import { ObsidianLoader } from "../fs/obsidian.js"; +import { Document } from "../../document.js"; + +const STANDARD_METADATA_FIELDS = [ + "created", + "path", + "source", + "lastAccessed", + "lastModified", +]; + +const FRONTMATTER_FIELDS = [ + "aBool", + "aFloat", + "anInt", + "anArray", + "aString", + "aDict", + "tags", +]; + +const DATAVIEW_FIELDS = ["dataview1", "dataview2", "dataview3"]; + +const directoryPath = path.resolve( + path.dirname(url.fileURLToPath(import.meta.url)), + "./example_data/obsidian" +); + +let docs: Document[]; + +beforeAll(async () => { + const loader = new ObsidianLoader(directoryPath); + docs = await loader.load(); +}); + +test("Test page content is loaded", async () => { + expect(docs.length).toBe(5); + docs.forEach((doc) => { + expect(doc.pageContent).toBeTruthy(); + }); +}); + +test("Test no additional metadata is collected if collectMetadata is false", async () => { + const noMetadataLoader = new ObsidianLoader(directoryPath, { + collectMetadata: false, + }); + const noMetadataDocs = await noMetadataLoader.load(); + + expect(noMetadataDocs.length).toBe(5); + expect( + noMetadataDocs.every( + (doc) => + Object.keys(doc.metadata).length === + Object.keys(STANDARD_METADATA_FIELDS).length && + Object.keys(doc.metadata).every((key) => + STANDARD_METADATA_FIELDS.includes(key) + ) + ) + ).toBe(true); +}); + +test("Test docs without frontmatter still have basic metadata", async () => { + const doc = docs.find((doc) => doc.metadata.source === "no_metadata.md"); + + if (!doc) { + fail("'no_metadata.md' not found."); + } + + expect( + Object.keys(doc.metadata).every((key) => + STANDARD_METADATA_FIELDS.includes(key) + ) + ).toBe(true); +}); + +test("Test standard frontmatter fields are loaded", async () => { + const doc = docs.find((doc) => doc.metadata.source === "frontmatter.md"); + + if (!doc) { + fail("'frontmatter.md' not found."); + } + + expect(Object.keys(doc.metadata)).toEqual( + expect.arrayContaining(STANDARD_METADATA_FIELDS.concat("tags")) + ); + + const tagsSet = new Set(doc.metadata.tags?.split(",")); + expect(tagsSet.has("journal/entry")).toBe(true); + expect(tagsSet.has("obsidian")).toBe(true); +}); + +test("Test a doc with non-yaml frontmatter still have basic metadata", async () => { + const doc = docs.find((doc) => doc.metadata.source === "bad_frontmatter.md"); + + if (!doc) { + fail("'bad_frontmatter.md' not found."); + } + + expect( + Object.keys(doc.metadata).every((key) => + STANDARD_METADATA_FIELDS.includes(key) + ) + ).toBe(true); +}); + +test("Test a doc with frontmatter and tags/dataview tags are all added to metadata", () => { + const doc = docs.find( + (doc) => doc.metadata.source === "tags_and_frontmatter.md" + ); + + if (!doc) { + fail("'tags_and_frontmatter.md' not found."); + } + + const expectedFields = [ + ...STANDARD_METADATA_FIELDS, + ...FRONTMATTER_FIELDS, + ...DATAVIEW_FIELDS, + ]; + expect(Object.keys(doc.metadata)).toEqual( + expect.arrayContaining(expectedFields) + ); +}); + +test("Test float metadata is loaded correctly", () => { + const doc = docs.find( + (doc) => doc.metadata.source === "tags_and_frontmatter.md" + ); + + if (!doc) { + fail("Document 'tags_and_frontmatter.md' not found."); + return; + } + + expect(doc.metadata.aFloat).toBe(13.12345); +}); + +test("Test int metadata is loaded correctly", () => { + const doc = docs.find( + (doc) => doc.metadata.source === "tags_and_frontmatter.md" + ); + + if (!doc) { + fail("Document 'tags_and_frontmatter.md' not found."); + return; + } + + expect(doc.metadata.anInt).toBe(15); +}); + +test("Test string metadata is loaded correctly", () => { + const doc = docs.find( + (doc) => doc.metadata.source === "tags_and_frontmatter.md" + ); + + if (!doc) { + fail("Document 'tags_and_frontmatter.md' not found."); + return; + } + + expect(doc.metadata.aString).toBe("string value"); +}); + +test("Test array metadata is loaded as a string", () => { + const doc = docs.find( + (doc) => doc.metadata.source === "tags_and_frontmatter.md" + ); + + if (!doc) { + fail("Document 'tags_and_frontmatter.md' not found."); + return; + } + + expect(doc.metadata.anArray).toBe('["one","two","three"]'); +}); + +test("Test dict metadata is stored as a string", () => { + const doc = docs.find( + (doc) => doc.metadata.source === "tags_and_frontmatter.md" + ); + + if (!doc) { + fail("Document 'tags_and_frontmatter.md' not found."); + return; + } + + expect(doc.metadata.aDict).toBe('{"dictId1":"58417","dictId2":1500}'); +}); diff --git a/langchain/src/load/import_constants.ts b/langchain/src/load/import_constants.ts index aecb48a2bde2..d45a9c6d6227 100644 --- a/langchain/src/load/import_constants.ts +++ b/langchain/src/load/import_constants.ts @@ -111,6 +111,7 @@ export const optionalImportEntrypoints = [ "langchain/document_loaders/fs/epub", "langchain/document_loaders/fs/csv", "langchain/document_loaders/fs/notion", + "langchain/document_loaders/fs/obsidian", "langchain/document_loaders/fs/unstructured", "langchain/document_loaders/fs/openai_whisper_audio", "langchain/document_loaders/fs/pptx", diff --git a/langchain/src/load/import_type.d.ts b/langchain/src/load/import_type.d.ts index 58f83e6a085b..9b2b3abbde95 100644 --- a/langchain/src/load/import_type.d.ts +++ b/langchain/src/load/import_type.d.ts @@ -331,6 +331,9 @@ export interface OptionalImportMap { "langchain/document_loaders/fs/notion"?: | typeof import("../document_loaders/fs/notion.js") | Promise; + "langchain/document_loaders/fs/obsidian"?: + | typeof import("../document_loaders/fs/obsidian.js") + | Promise; "langchain/document_loaders/fs/unstructured"?: | typeof import("../document_loaders/fs/unstructured.js") | Promise; From 215594ec48665b4ac9cd26c391691e156318c960 Mon Sep 17 00:00:00 2001 From: Brace Sproul Date: Mon, 4 Dec 2023 19:41:47 -0800 Subject: [PATCH 33/44] all[patch]: Add .turbo to files removed by yarn clean (#3540) --- langchain-core/package.json | 2 +- langchain/package.json | 2 +- libs/langchain-anthropic/package.json | 2 +- libs/langchain-openai/package.json | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/langchain-core/package.json b/langchain-core/package.json index a11c036e3a4d..09a6759cd513 100644 --- a/langchain-core/package.json +++ b/langchain-core/package.json @@ -22,7 +22,7 @@ "lint:dpdm": "dpdm --exit-code circular:1 --no-warning --no-tree src/*.ts src/**/*.ts", "lint": "yarn lint:eslint && yarn lint:dpdm", "lint:fix": "yarn lint:eslint --fix && yarn lint:dpdm", - "clean": "rimraf dist/ && NODE_OPTIONS=--max-old-space-size=4096 node scripts/create-entrypoints.js pre", + "clean": "rimraf .turbo/ dist/ && NODE_OPTIONS=--max-old-space-size=4096 node scripts/create-entrypoints.js pre", "prepack": "yarn build", "release": "release-it --only-version --config .release-it.json", "test": "NODE_OPTIONS=--experimental-vm-modules jest --testPathIgnorePatterns=\\.int\\.test.ts --testTimeout 30000 --maxWorkers=50%", diff --git a/langchain/package.json b/langchain/package.json index 984287ff1edc..b04955305af4 100644 --- a/langchain/package.json +++ b/langchain/package.json @@ -860,7 +860,7 @@ "lint": "yarn lint:eslint && yarn lint:dpdm", "lint:fix": "yarn lint:eslint --fix && yarn lint:dpdm", "precommit": "lint-staged", - "clean": "rimraf dist/ && NODE_OPTIONS=--max-old-space-size=4096 node scripts/create-entrypoints.js pre", + "clean": "rimraf .turbo/ dist/ && NODE_OPTIONS=--max-old-space-size=4096 node scripts/create-entrypoints.js pre", "prepack": "yarn build", "release": "release-it --only-version --config .release-it.json", "test": "NODE_OPTIONS=--experimental-vm-modules jest --testPathIgnorePatterns=\\.int\\.test.ts --testTimeout 30000 --maxWorkers=50%", diff --git a/libs/langchain-anthropic/package.json b/libs/langchain-anthropic/package.json index 1dc1841c789f..2a474f1a772e 100644 --- a/libs/langchain-anthropic/package.json +++ b/libs/langchain-anthropic/package.json @@ -22,7 +22,7 @@ "lint:dpdm": "dpdm --exit-code circular:1 --no-warning --no-tree src/*.ts src/**/*.ts", "lint": "yarn lint:eslint && yarn lint:dpdm", "lint:fix": "yarn lint:eslint --fix && yarn lint:dpdm", - "clean": "rimraf dist/ && NODE_OPTIONS=--max-old-space-size=4096 node scripts/create-entrypoints.js pre", + "clean": "rimraf .turbo/ dist/ && NODE_OPTIONS=--max-old-space-size=4096 node scripts/create-entrypoints.js pre", "prepack": "yarn build", "release": "release-it --only-version --config .release-it.json", "test": "NODE_OPTIONS=--experimental-vm-modules jest --testPathIgnorePatterns=\\.int\\.test.ts --testTimeout 30000 --maxWorkers=50%", diff --git a/libs/langchain-openai/package.json b/libs/langchain-openai/package.json index 9c55ce2f4e66..0144eae8f936 100644 --- a/libs/langchain-openai/package.json +++ b/libs/langchain-openai/package.json @@ -22,7 +22,7 @@ "lint:dpdm": "dpdm --exit-code circular:1 --no-warning --no-tree src/*.ts src/**/*.ts", "lint": "yarn lint:eslint && yarn lint:dpdm", "lint:fix": "yarn lint:eslint --fix && yarn lint:dpdm", - "clean": "rimraf dist/ && NODE_OPTIONS=--max-old-space-size=4096 node scripts/create-entrypoints.js pre", + "clean": "rimraf .turbo/ dist/ && NODE_OPTIONS=--max-old-space-size=4096 node scripts/create-entrypoints.js pre", "prepack": "yarn build", "release": "release-it --only-version --config .release-it.json", "test": "NODE_OPTIONS=--experimental-vm-modules jest --testPathIgnorePatterns=\\.int\\.test.ts --testTimeout 30000 --maxWorkers=50%", From 2c324c4238b81c0ccfcbaf3f3310b64293638ee3 Mon Sep 17 00:00:00 2001 From: Volodymyr Machula Date: Tue, 5 Dec 2023 04:59:53 +0100 Subject: [PATCH 34/44] Add Connery Tool and Toolkit (#3499) * Add ConneryApiClient class * Intermediate sate * Fix ConneryToolkit typo and update ConneryService method name * Init docs * Update docs * Fix imports in docs * Create entry points for the tool and toolkit * Fix the docs issue --- docs/api_refs/typedoc.json | 2 + .../docs/integrations/toolkits/connery.mdx | 25 ++ .../docs/integrations/tools/connery.mdx | 25 ++ .../test-exports-bun/src/entrypoints.js | 2 + .../test-exports-cf/src/entrypoints.js | 2 + .../test-exports-cjs/src/entrypoints.js | 2 + .../test-exports-esbuild/src/entrypoints.js | 2 + .../test-exports-esm/src/entrypoints.js | 2 + .../test-exports-vercel/src/entrypoints.js | 2 + .../test-exports-vite/src/entrypoints.js | 2 + examples/.env.example | 2 + examples/src/agents/connery_mrkl.ts | 58 +++ examples/src/tools/connery.ts | 37 ++ langchain/.env.example | 2 + langchain/.gitignore | 6 + langchain/package.json | 16 + langchain/scripts/create-entrypoints.js | 20 +- .../src/agents/toolkits/connery/index.ts | 35 ++ langchain/src/load/import_map.ts | 2 + langchain/src/tools/connery.ts | 353 ++++++++++++++++++ 20 files changed, 588 insertions(+), 9 deletions(-) create mode 100644 docs/core_docs/docs/integrations/toolkits/connery.mdx create mode 100644 docs/core_docs/docs/integrations/tools/connery.mdx create mode 100644 examples/src/agents/connery_mrkl.ts create mode 100644 examples/src/tools/connery.ts create mode 100644 langchain/src/agents/toolkits/connery/index.ts create mode 100644 langchain/src/tools/connery.ts diff --git a/docs/api_refs/typedoc.json b/docs/api_refs/typedoc.json index 6f28212aacb8..044d8a32270f 100644 --- a/docs/api_refs/typedoc.json +++ b/docs/api_refs/typedoc.json @@ -24,6 +24,7 @@ "./langchain/src/agents/load.ts", "./langchain/src/agents/toolkits/index.ts", "./langchain/src/agents/toolkits/aws_sfn.ts", + "./langchain/src/agents/toolkits/connery/index.ts", "./langchain/src/agents/toolkits/sql/index.ts", "./langchain/src/agents/format_scratchpad/openai_functions.ts", "./langchain/src/agents/format_scratchpad/openai_tools.ts", @@ -38,6 +39,7 @@ "./langchain/src/tools/aws_lambda.ts", "./langchain/src/tools/aws_sfn.ts", "./langchain/src/tools/calculator.ts", + "./langchain/src/tools/connery.ts", "./langchain/src/tools/render.ts", "./langchain/src/tools/sql.ts", "./langchain/src/tools/webbrowser.ts", diff --git a/docs/core_docs/docs/integrations/toolkits/connery.mdx b/docs/core_docs/docs/integrations/toolkits/connery.mdx new file mode 100644 index 000000000000..dc2d3c25e777 --- /dev/null +++ b/docs/core_docs/docs/integrations/toolkits/connery.mdx @@ -0,0 +1,25 @@ +import CodeBlock from "@theme/CodeBlock"; +import Example from "@examples/agents/connery_mrkl.ts"; + +# Connery Actions Toolkit + +Using this toolkit, you can integrate Connery actions into your LangChain agents and chains. + +## What is Connery? + +Connery is an open-source plugin infrastructure for AI. + +With Connery, you can easily create a custom plugin, which is essentially a set of actions, and use them in your LangChain agents and chains. +Connery will handle the rest: runtime, authorization, secret management, access management, audit logs, and other vital features. +Also, you can find a lot of ready-to-use plugins from our community. + +Learn more about Connery: + +- GitHub repository: https://github.com/connery-io/connery-platform +- Documentation: https://docs.connery.io + +## Usage + +This example shows how to create an agent with Connery actions using the Connery Actions Toolkit. + +{Example} diff --git a/docs/core_docs/docs/integrations/tools/connery.mdx b/docs/core_docs/docs/integrations/tools/connery.mdx new file mode 100644 index 000000000000..67bfca14821b --- /dev/null +++ b/docs/core_docs/docs/integrations/tools/connery.mdx @@ -0,0 +1,25 @@ +import CodeBlock from "@theme/CodeBlock"; +import Example from "@examples/tools/connery.ts"; + +# Connery Actions Tool + +Using this tool, you can integrate individual Connery actions into your LangChain agents and chains. + +## What is Connery? + +Connery is an open-source plugin infrastructure for AI. + +With Connery, you can easily create a custom plugin, which is essentially a set of actions, and use them in your LangChain agents and chains. +Connery will handle the rest: runtime, authorization, secret management, access management, audit logs, and other vital features. +Also, you can find a lot of ready-to-use plugins from our community. + +Learn more about Connery: + +- GitHub repository: https://github.com/connery-io/connery-platform +- Documentation: https://docs.connery.io + +## Usage + +This example shows how to create a tool for one specific Connery action and call it. + +{Example} diff --git a/environment_tests/test-exports-bun/src/entrypoints.js b/environment_tests/test-exports-bun/src/entrypoints.js index 61b2f0553453..3f823c50181f 100644 --- a/environment_tests/test-exports-bun/src/entrypoints.js +++ b/environment_tests/test-exports-bun/src/entrypoints.js @@ -2,6 +2,7 @@ export * from "langchain/load"; export * from "langchain/load/serializable"; export * from "langchain/agents"; export * from "langchain/agents/toolkits"; +export * from "langchain/agents/toolkits/connery"; export * from "langchain/agents/format_scratchpad"; export * from "langchain/agents/format_scratchpad/openai_tools"; export * from "langchain/agents/format_scratchpad/log"; @@ -12,6 +13,7 @@ export * from "langchain/agents/xml/output_parser"; export * from "langchain/agents/openai/output_parser"; export * from "langchain/base_language"; export * from "langchain/tools"; +export * from "langchain/tools/connery"; export * from "langchain/tools/render"; export * from "langchain/tools/google_places"; export * from "langchain/chains"; diff --git a/environment_tests/test-exports-cf/src/entrypoints.js b/environment_tests/test-exports-cf/src/entrypoints.js index 61b2f0553453..3f823c50181f 100644 --- a/environment_tests/test-exports-cf/src/entrypoints.js +++ b/environment_tests/test-exports-cf/src/entrypoints.js @@ -2,6 +2,7 @@ export * from "langchain/load"; export * from "langchain/load/serializable"; export * from "langchain/agents"; export * from "langchain/agents/toolkits"; +export * from "langchain/agents/toolkits/connery"; export * from "langchain/agents/format_scratchpad"; export * from "langchain/agents/format_scratchpad/openai_tools"; export * from "langchain/agents/format_scratchpad/log"; @@ -12,6 +13,7 @@ export * from "langchain/agents/xml/output_parser"; export * from "langchain/agents/openai/output_parser"; export * from "langchain/base_language"; export * from "langchain/tools"; +export * from "langchain/tools/connery"; export * from "langchain/tools/render"; export * from "langchain/tools/google_places"; export * from "langchain/chains"; diff --git a/environment_tests/test-exports-cjs/src/entrypoints.js b/environment_tests/test-exports-cjs/src/entrypoints.js index 725b9cd0477c..48ed405030ab 100644 --- a/environment_tests/test-exports-cjs/src/entrypoints.js +++ b/environment_tests/test-exports-cjs/src/entrypoints.js @@ -2,6 +2,7 @@ const load = require("langchain/load"); const load_serializable = require("langchain/load/serializable"); const agents = require("langchain/agents"); const agents_toolkits = require("langchain/agents/toolkits"); +const agents_toolkits_connery = require("langchain/agents/toolkits/connery"); const agents_format_scratchpad = require("langchain/agents/format_scratchpad"); const agents_format_scratchpad_openai_tools = require("langchain/agents/format_scratchpad/openai_tools"); const agents_format_scratchpad_log = require("langchain/agents/format_scratchpad/log"); @@ -12,6 +13,7 @@ const agents_xml_output_parser = require("langchain/agents/xml/output_parser"); const agents_openai_output_parser = require("langchain/agents/openai/output_parser"); const base_language = require("langchain/base_language"); const tools = require("langchain/tools"); +const tools_connery = require("langchain/tools/connery"); const tools_render = require("langchain/tools/render"); const tools_google_places = require("langchain/tools/google_places"); const chains = require("langchain/chains"); diff --git a/environment_tests/test-exports-esbuild/src/entrypoints.js b/environment_tests/test-exports-esbuild/src/entrypoints.js index 9c4a916789c5..b8a0473cfcc2 100644 --- a/environment_tests/test-exports-esbuild/src/entrypoints.js +++ b/environment_tests/test-exports-esbuild/src/entrypoints.js @@ -2,6 +2,7 @@ import * as load from "langchain/load"; import * as load_serializable from "langchain/load/serializable"; import * as agents from "langchain/agents"; import * as agents_toolkits from "langchain/agents/toolkits"; +import * as agents_toolkits_connery from "langchain/agents/toolkits/connery"; import * as agents_format_scratchpad from "langchain/agents/format_scratchpad"; import * as agents_format_scratchpad_openai_tools from "langchain/agents/format_scratchpad/openai_tools"; import * as agents_format_scratchpad_log from "langchain/agents/format_scratchpad/log"; @@ -12,6 +13,7 @@ import * as agents_xml_output_parser from "langchain/agents/xml/output_parser"; import * as agents_openai_output_parser from "langchain/agents/openai/output_parser"; import * as base_language from "langchain/base_language"; import * as tools from "langchain/tools"; +import * as tools_connery from "langchain/tools/connery"; import * as tools_render from "langchain/tools/render"; import * as tools_google_places from "langchain/tools/google_places"; import * as chains from "langchain/chains"; diff --git a/environment_tests/test-exports-esm/src/entrypoints.js b/environment_tests/test-exports-esm/src/entrypoints.js index 9c4a916789c5..b8a0473cfcc2 100644 --- a/environment_tests/test-exports-esm/src/entrypoints.js +++ b/environment_tests/test-exports-esm/src/entrypoints.js @@ -2,6 +2,7 @@ import * as load from "langchain/load"; import * as load_serializable from "langchain/load/serializable"; import * as agents from "langchain/agents"; import * as agents_toolkits from "langchain/agents/toolkits"; +import * as agents_toolkits_connery from "langchain/agents/toolkits/connery"; import * as agents_format_scratchpad from "langchain/agents/format_scratchpad"; import * as agents_format_scratchpad_openai_tools from "langchain/agents/format_scratchpad/openai_tools"; import * as agents_format_scratchpad_log from "langchain/agents/format_scratchpad/log"; @@ -12,6 +13,7 @@ import * as agents_xml_output_parser from "langchain/agents/xml/output_parser"; import * as agents_openai_output_parser from "langchain/agents/openai/output_parser"; import * as base_language from "langchain/base_language"; import * as tools from "langchain/tools"; +import * as tools_connery from "langchain/tools/connery"; import * as tools_render from "langchain/tools/render"; import * as tools_google_places from "langchain/tools/google_places"; import * as chains from "langchain/chains"; diff --git a/environment_tests/test-exports-vercel/src/entrypoints.js b/environment_tests/test-exports-vercel/src/entrypoints.js index 61b2f0553453..3f823c50181f 100644 --- a/environment_tests/test-exports-vercel/src/entrypoints.js +++ b/environment_tests/test-exports-vercel/src/entrypoints.js @@ -2,6 +2,7 @@ export * from "langchain/load"; export * from "langchain/load/serializable"; export * from "langchain/agents"; export * from "langchain/agents/toolkits"; +export * from "langchain/agents/toolkits/connery"; export * from "langchain/agents/format_scratchpad"; export * from "langchain/agents/format_scratchpad/openai_tools"; export * from "langchain/agents/format_scratchpad/log"; @@ -12,6 +13,7 @@ export * from "langchain/agents/xml/output_parser"; export * from "langchain/agents/openai/output_parser"; export * from "langchain/base_language"; export * from "langchain/tools"; +export * from "langchain/tools/connery"; export * from "langchain/tools/render"; export * from "langchain/tools/google_places"; export * from "langchain/chains"; diff --git a/environment_tests/test-exports-vite/src/entrypoints.js b/environment_tests/test-exports-vite/src/entrypoints.js index 61b2f0553453..3f823c50181f 100644 --- a/environment_tests/test-exports-vite/src/entrypoints.js +++ b/environment_tests/test-exports-vite/src/entrypoints.js @@ -2,6 +2,7 @@ export * from "langchain/load"; export * from "langchain/load/serializable"; export * from "langchain/agents"; export * from "langchain/agents/toolkits"; +export * from "langchain/agents/toolkits/connery"; export * from "langchain/agents/format_scratchpad"; export * from "langchain/agents/format_scratchpad/openai_tools"; export * from "langchain/agents/format_scratchpad/log"; @@ -12,6 +13,7 @@ export * from "langchain/agents/xml/output_parser"; export * from "langchain/agents/openai/output_parser"; export * from "langchain/base_language"; export * from "langchain/tools"; +export * from "langchain/tools/connery"; export * from "langchain/tools/render"; export * from "langchain/tools/google_places"; export * from "langchain/chains"; diff --git a/examples/.env.example b/examples/.env.example index 01c8ab07fa90..f34cfdf1d2db 100644 --- a/examples/.env.example +++ b/examples/.env.example @@ -15,6 +15,8 @@ AZURE_OPENAI_API_COMPLETIONS_DEPLOYMENT_NAME=ADD_YOURS_HERE # Azure Portal -> Co AZURE_OPENAI_API_EMBEDDINGS_DEPLOYMENT_NAME=ADD_YOURS_HERE # Azure Portal -> Cognitive Services -> OpenAI -> Choose your instance -> Go to Azure OpenAI Studio -> Deployments AZURE_OPENAI_API_VERSION=ADD_YOURS_HERE # Azure Portal -> Cognitive Services -> OpenAI -> Choose your instance -> Go to Azure OpenAI Studio -> Completions/Chat -> Choose Deployment -> View Code AZURE_OPENAI_BASE_PATH=ADD_YOURS_HERE # Azure Portal -> Cognitive Services -> OpenAI -> Choose your instance -> Endpoint (optional) +CONNERY_RUNNER_URL=ADD_YOURS_HERE +CONNERY_RUNNER_API_KEY=ADD_YOURS_HERE ELASTIC_URL=ADD_YOURS_HERE # http://127.0.0.1:9200 ELASTIC_USERNAME="elastic" ELASTIC_PASSWORD=ADD_YOURS_HERE # Password for the 'elastic' user (at least 6 characters) diff --git a/examples/src/agents/connery_mrkl.ts b/examples/src/agents/connery_mrkl.ts new file mode 100644 index 000000000000..4fc55c9c45d9 --- /dev/null +++ b/examples/src/agents/connery_mrkl.ts @@ -0,0 +1,58 @@ +import { OpenAI } from "langchain/llms/openai"; +import { initializeAgentExecutorWithOptions } from "langchain/agents"; +import { ConneryToolkit } from "langchain/agents/toolkits/connery"; +import { ConneryService } from "langchain/tools/connery"; + +/** + * This example shows how to create an agent with Connery actions using the Connery Actions Toolkit. + * + * Connery is an open-source plugin infrastructure for AI. + * Source code: https://github.com/connery-io/connery-platform + * + * To run this example, you need to do some preparation: + * 1. Set up the Connery runner. See a quick start guide here: https://docs.connery.io/docs/platform/quick-start/ + * 2. Intsall the "Summarization" plugin (https://github.com/connery-io/summarization-plugin) on the runner. + * 3. Install the "Gmail" plugin (https://github.com/connery-io/gmail) on the runner. + * 4. Set environment variables CONNERY_RUNNER_URL and CONNERY_RUNNER_API_KEY in the ./examples/.env file of this repository. + * + * If you want to use only one particular Connery action in your agent, + * check out an example here: ./examples/src/tools/connery.ts + */ + +const model = new OpenAI({ temperature: 0 }); +const conneryService = new ConneryService(); +const conneryToolkit = await ConneryToolkit.createInstance(conneryService); + +const executor = await initializeAgentExecutorWithOptions( + conneryToolkit.tools, + model, + { + agentType: "zero-shot-react-description", + verbose: true, + } +); + +/** + * In this example we use two Connery actions: + * 1. "Summarize public webpage" from the "Summarization" plugin. + * 2. "Send email" from the "Gmail" plugin. + */ +const input = + "Make a short summary of the webpage http://www.paulgraham.com/vb.html in three sentences " + + "and send it to test@example.com. Include the link to the webpage into the body of the email."; +const result = await executor.invoke({ input }); +console.log(result.output); + +/** + * As a result, you should receive an email similar to this: + * + * Subject: Summary of "Life is Short" + * Body: Here is a summary of the webpage "Life is Short" by Paul Graham: + * The author reflects on the shortness of life and how having children has made them realize + * the limited time they have. They argue that life is too short for unnecessary things, + * or "bullshit," and that one should prioritize avoiding it. + * They also discuss the importance of actively seeking out things that matter and not waiting to do them. + * The author suggests pruning unnecessary things, savoring the time one has, and not waiting to do what truly matters. + * They also discuss the effect of how one lives on the length of their life and the importance of being conscious of time. + * Link to webpage: http://www.paulgraham.com/vb.html + */ diff --git a/examples/src/tools/connery.ts b/examples/src/tools/connery.ts new file mode 100644 index 000000000000..deee4acff769 --- /dev/null +++ b/examples/src/tools/connery.ts @@ -0,0 +1,37 @@ +import { ConneryService } from "langchain/tools/connery"; + +/** + * This example shows how to create a tool for one specific Connery action and call it. + * + * Connery is an open-source plugin infrastructure for AI. + * Source code: https://github.com/connery-io/connery-platform + * + * To run this example, you need to do some preparation: + * 1. Set up the Connery runner. See a quick start guide here: https://docs.connery.io/docs/platform/quick-start/ + * 2. Install the "Gmail" plugin (https://github.com/connery-io/gmail) on the runner. + * 3. Set environment variables CONNERY_RUNNER_URL and CONNERY_RUNNER_API_KEY in the ./examples/.env file of this repository. + * + * If you want to use several Connery actions in your agent, check out the Connery Toolkit. + * Example of using Connery Toolkit: ./examples/src/agents/connery_mrkl.ts + */ + +const conneryService = new ConneryService(); + +/** + * The "getAction" method fetches the action from the Connery runner by ID, + * constructs a LangChain tool object from it, and returns it to the caller. + * + * In this example, we use the ID of the "Send email" action from the "Gmail" plugin. + * You can find action IDs in the Connery runner. + */ +const tool = await conneryService.getAction("CABC80BB79C15067CA983495324AE709"); + +/** + * The "call" method of the tool takes a plain English prompt + * with all the information needed to run the Connery action behind the scenes. + */ +const result = await tool.call( + "Send an email to test@example.com with the subject 'Test email' and the body 'This is a test email sent from Langchain Connery tool.'" +); + +console.log(result); diff --git a/langchain/.env.example b/langchain/.env.example index d5c2d249c0b5..aa7c2994e9a8 100644 --- a/langchain/.env.example +++ b/langchain/.env.example @@ -13,6 +13,8 @@ AZURE_OPENAI_API_COMPLETIONS_DEPLOYMENT_NAME=ADD_YOURS_HERE AZURE_OPENAI_API_EMBEDDINGS_DEPLOYMENT_NAME=ADD_YOURS_HERE AZURE_OPENAI_API_VERSION=ADD_YOURS_HERE AZURE_OPENAI_BASE_PATH=ADD_YOURS_HERE +CONNERY_RUNNER_URL=ADD_YOURS_HERE +CONNERY_RUNNER_API_KEY=ADD_YOURS_HERE CONVEX_URL=ADD_YOURS_HERE ELASTIC_URL=http://127.0.0.1:9200 OPENSEARCH_URL=http://127.0.0.1:9200 diff --git a/langchain/.gitignore b/langchain/.gitignore index e45de564ddd8..99eaf327b22a 100644 --- a/langchain/.gitignore +++ b/langchain/.gitignore @@ -16,6 +16,9 @@ agents/toolkits.d.ts agents/toolkits/aws_sfn.cjs agents/toolkits/aws_sfn.js agents/toolkits/aws_sfn.d.ts +agents/toolkits/connery.cjs +agents/toolkits/connery.js +agents/toolkits/connery.d.ts agents/toolkits/sql.cjs agents/toolkits/sql.js agents/toolkits/sql.d.ts @@ -58,6 +61,9 @@ tools/aws_sfn.d.ts tools/calculator.cjs tools/calculator.js tools/calculator.d.ts +tools/connery.cjs +tools/connery.js +tools/connery.d.ts tools/render.cjs tools/render.js tools/render.d.ts diff --git a/langchain/package.json b/langchain/package.json index b04955305af4..b7635eed06e4 100644 --- a/langchain/package.json +++ b/langchain/package.json @@ -28,6 +28,9 @@ "agents/toolkits/aws_sfn.cjs", "agents/toolkits/aws_sfn.js", "agents/toolkits/aws_sfn.d.ts", + "agents/toolkits/connery.cjs", + "agents/toolkits/connery.js", + "agents/toolkits/connery.d.ts", "agents/toolkits/sql.cjs", "agents/toolkits/sql.js", "agents/toolkits/sql.d.ts", @@ -70,6 +73,9 @@ "tools/calculator.cjs", "tools/calculator.js", "tools/calculator.d.ts", + "tools/connery.cjs", + "tools/connery.js", + "tools/connery.d.ts", "tools/render.cjs", "tools/render.js", "tools/render.d.ts", @@ -1492,6 +1498,11 @@ "import": "./agents/toolkits/aws_sfn.js", "require": "./agents/toolkits/aws_sfn.cjs" }, + "./agents/toolkits/connery": { + "types": "./agents/toolkits/connery.d.ts", + "import": "./agents/toolkits/connery.js", + "require": "./agents/toolkits/connery.cjs" + }, "./agents/toolkits/sql": { "types": "./agents/toolkits/sql.d.ts", "import": "./agents/toolkits/sql.js", @@ -1562,6 +1573,11 @@ "import": "./tools/calculator.js", "require": "./tools/calculator.cjs" }, + "./tools/connery": { + "types": "./tools/connery.d.ts", + "import": "./tools/connery.js", + "require": "./tools/connery.cjs" + }, "./tools/render": { "types": "./tools/render.d.ts", "import": "./tools/render.js", diff --git a/langchain/scripts/create-entrypoints.js b/langchain/scripts/create-entrypoints.js index 69ac7bfd5740..65cc773bdf0c 100644 --- a/langchain/scripts/create-entrypoints.js +++ b/langchain/scripts/create-entrypoints.js @@ -15,6 +15,7 @@ const entrypoints = { "agents/load": "agents/load", "agents/toolkits": "agents/toolkits/index", "agents/toolkits/aws_sfn": "agents/toolkits/aws_sfn", + "agents/toolkits/connery": "agents/toolkits/connery/index", "agents/toolkits/sql": "agents/toolkits/sql/index", "agents/format_scratchpad": "agents/format_scratchpad/openai_functions", "agents/format_scratchpad/openai_tools": @@ -33,6 +34,7 @@ const entrypoints = { "tools/aws_lambda": "tools/aws_lambda", "tools/aws_sfn": "tools/aws_sfn", "tools/calculator": "tools/calculator", + "tools/connery": "tools/connery", "tools/render": "tools/render", "tools/sql": "tools/sql", "tools/webbrowser": "tools/webbrowser", @@ -664,7 +666,7 @@ const generateImportMap = () => { fs.writeFileSync( `../${pkg}/${importMapPath}`, "// Auto-generated by `scripts/create-entrypoints.js`. Do not edit manually.\n\n" + - contents + contents ); }; @@ -686,17 +688,17 @@ const generateImportTypes = () => { export interface OptionalImportMap { ${Object.keys(entrypoints) - .filter((key) => !deprecatedNodeOnly.includes(key)) - .filter((key) => requiresOptionalDependency.includes(key)) - .map((key) => importStatement(key, entrypoints[key])) - .join("\n")} + .filter((key) => !deprecatedNodeOnly.includes(key)) + .filter((key) => requiresOptionalDependency.includes(key)) + .map((key) => importStatement(key, entrypoints[key])) + .join("\n")} } export interface SecretMap { ${[...identifySecrets()] - .sort() - .map((secret) => ` ${secret}?: string;`) - .join("\n")} + .sort() + .map((secret) => ` ${secret}?: string;`) + .join("\n")} } ` ); @@ -721,7 +723,7 @@ const generateImportConstants = () => { fs.writeFileSync( `../${pkg}/${importConstantsPath}`, "// Auto-generated by `scripts/create-entrypoints.js`. Do not edit manually.\n\nexport const optionalImportEntrypoints = [\n" + - contents + contents ); }; diff --git a/langchain/src/agents/toolkits/connery/index.ts b/langchain/src/agents/toolkits/connery/index.ts new file mode 100644 index 000000000000..a8b54f3d54d3 --- /dev/null +++ b/langchain/src/agents/toolkits/connery/index.ts @@ -0,0 +1,35 @@ +import { Tool } from "@langchain/core/tools"; +import { Toolkit } from "../base.js"; +import { ConneryService } from "../../../tools/connery.js"; + +/** + * A toolkit for working with Connery actions. + * + * Connery is an open-source plugin infrastructure for AI. + * Source code: https://github.com/connery-io/connery-platform + * + * See an example of using this toolkit here: `./examples/src/agents/connery_mrkl.ts` + * @extends Toolkit + */ +export class ConneryToolkit extends Toolkit { + tools: Tool[]; + + /** + * Creates a ConneryToolkit instance based on the provided ConneryService instance. + * It populates the tools property of the ConneryToolkit instance with the list of + * available tools from the ConneryService instance. + * @param conneryService The ConneryService instance. + * @returns A Promise that resolves to a ConneryToolkit instance. + */ + static async createInstance( + conneryService: ConneryService + ): Promise { + const toolkit = new ConneryToolkit(); + toolkit.tools = []; + + const actions = await conneryService.listActions(); + toolkit.tools.push(...actions); + + return toolkit; + } +} diff --git a/langchain/src/load/import_map.ts b/langchain/src/load/import_map.ts index a1d511062784..fa6ef233e228 100644 --- a/langchain/src/load/import_map.ts +++ b/langchain/src/load/import_map.ts @@ -3,6 +3,7 @@ export * as load__serializable from "../load/serializable.js"; export * as agents from "../agents/index.js"; export * as agents__toolkits from "../agents/toolkits/index.js"; +export * as agents__toolkits__connery from "../agents/toolkits/connery/index.js"; export * as agents__format_scratchpad from "../agents/format_scratchpad/openai_functions.js"; export * as agents__format_scratchpad__openai_tools from "../agents/format_scratchpad/openai_tools.js"; export * as agents__format_scratchpad__log from "../agents/format_scratchpad/log.js"; @@ -13,6 +14,7 @@ export * as agents__xml__output_parser from "../agents/xml/output_parser.js"; export * as agents__openai__output_parser from "../agents/openai/output_parser.js"; export * as base_language from "../base_language/index.js"; export * as tools from "../tools/index.js"; +export * as tools__connery from "../tools/connery.js"; export * as tools__render from "../tools/render.js"; export * as tools__google_places from "../tools/google_places.js"; export * as chains from "../chains/index.js"; diff --git a/langchain/src/tools/connery.ts b/langchain/src/tools/connery.ts new file mode 100644 index 000000000000..5ee594ba85a9 --- /dev/null +++ b/langchain/src/tools/connery.ts @@ -0,0 +1,353 @@ +import { AsyncCaller, AsyncCallerParams } from "../util/async_caller.js"; +import { getEnvironmentVariable } from "../util/env.js"; +import { Tool } from "./base.js"; + +/** + * An object containing configuration parameters for the ConneryService class. + * @extends AsyncCallerParams + */ +export interface ConneryServiceParams extends AsyncCallerParams { + runnerUrl: string; + apiKey: string; +} + +type ApiResponse = { + status: "success"; + data: T; +}; + +type ApiErrorResponse = { + status: "error"; + error: { + message: string; + }; +}; + +type Parameter = { + key: string; + title: string; + description: string; + type: string; + validation?: { + required?: boolean; + }; +}; + +type Action = { + id: string; + key: string; + title: string; + description: string; + type: string; + inputParameters: Parameter[]; + outputParameters: Parameter[]; + pluginId: string; +}; + +type Input = { + [key: string]: string; +}; + +type Output = { + [key: string]: string; +}; + +type RunActionResult = { + output: Output; + used: { + actionId: string; + input: Input; + }; +}; + +/** + * A LangChain Tool object wrapping a Connery action. + * @extends Tool + */ +export class ConneryAction extends Tool { + name: string; + + description: string; + + /** + * Creates a ConneryAction instance based on the provided Connery action. + * @param _action The Connery action. + * @param _service The ConneryService instance. + * @returns A ConneryAction instance. + */ + constructor(protected _action: Action, protected _service: ConneryService) { + super(); + + this.name = this._action.title; + this.description = this.getDescription(); + } + + /** + * Runs the Connery action. + * @param prompt This is a plain English prompt with all the information needed to run the action. + * @returns A promise that resolves to a JSON string containing the output of the action. + */ + protected _call(prompt: string): Promise { + return this._service.runAction(this._action.id, prompt); + } + + /** + * Returns the description of the Connery action. + * @returns A string containing the description of the Connery action together with the instructions on how to use it. + */ + protected getDescription(): string { + const { title, description } = this._action; + const inputParameters = this.prepareJsonForTemplate( + this._action.inputParameters + ); + const example1InputParametersSchema = this.prepareJsonForTemplate([ + { + key: "recipient", + title: "Email Recipient", + description: "Email address of the email recipient.", + type: "string", + validation: { + required: true, + }, + }, + { + key: "subject", + title: "Email Subject", + description: "Subject of the email.", + type: "string", + validation: { + required: true, + }, + }, + { + key: "body", + title: "Email Body", + description: "Body of the email.", + type: "string", + validation: { + required: true, + }, + }, + ]); + + const descriptionTemplate = + "# Instructions about tool input:\n" + + "The input to this tool is a plain English prompt with all the input parameters needed to call it. " + + "The input parameters schema of this tool is provided below. " + + "Use the input parameters schema to construct the prompt for the tool. " + + "If the input parameter is required in the schema, it must be provided in the prompt. " + + "Do not come up with the values for the input parameters yourself. " + + "If you do not have enough information to fill in the input parameter, ask the user to provide it. " + + "See examples below on how to construct the prompt based on the provided tool information. " + + "\n\n" + + "# Instructions about tool output:\n" + + "The output of this tool is a JSON string. " + + "Retrieve the output parameters from the JSON string and use them in the next tool. " + + "Do not return the JSON string as the output of the tool. " + + "\n\n" + + "# Example:\n" + + "Tool information:\n" + + "- Title: Send email\n" + + "- Description: Send an email to a recipient.\n" + + `- Input parameters schema in JSON fromat: ${example1InputParametersSchema}\n` + + "The tool input prompt:\n" + + "recipient: test@example.com, subject: 'Test email', body: 'This is a test email sent from Langchain Connery tool.'\n" + + "\n\n" + + "# The tool information\n" + + `- Title: ${title}\n` + + `- Description: ${description}\n` + + `- Input parameters schema in JSON fromat: ${inputParameters}\n`; + + return descriptionTemplate; + } + + /** + * Converts the provided object to a JSON string and escapes '{' and '}' characters. + * @param obj The object to convert to a JSON string. + * @returns A string containing the JSON representation of the provided object with '{' and '}' characters escaped. + */ + // eslint-disable-next-line @typescript-eslint/no-explicit-any + protected prepareJsonForTemplate(obj: any): string { + // Convert the object to a JSON string + const jsonString = JSON.stringify(obj); + + // Replace '{' with '{{' and '}' with '}}' + const escapedJSON = jsonString.replace(/{/g, "{{").replace(/}/g, "}}"); + + return escapedJSON; + } +} + +/** + * A service for working with Connery actions. + * + * Connery is an open-source plugin infrastructure for AI. + * Source code: https://github.com/connery-io/connery-platform + */ +export class ConneryService { + protected runnerUrl: string; + + protected apiKey: string; + + protected asyncCaller: AsyncCaller; + + /** + * Creates a ConneryService instance. + * @param params A ConneryServiceParams object. + * If not provided, the values are retrieved from the CONNERY_RUNNER_URL + * and CONNERY_RUNNER_API_KEY environment variables. + * @returns A ConneryService instance. + */ + constructor(params?: ConneryServiceParams) { + const runnerUrl = + params?.runnerUrl ?? getEnvironmentVariable("CONNERY_RUNNER_URL"); + const apiKey = + params?.apiKey ?? getEnvironmentVariable("CONNERY_RUNNER_API_KEY"); + + if (!runnerUrl || !apiKey) { + throw new Error( + "CONNERY_RUNNER_URL and CONNERY_RUNNER_API_KEY environment variables must be set." + ); + } + + this.runnerUrl = runnerUrl; + this.apiKey = apiKey; + + this.asyncCaller = new AsyncCaller(params ?? {}); + } + + /** + * Returns the list of Connery actions wrapped as a LangChain Tool objects. + * @returns A promise that resolves to an array of ConneryAction objects. + */ + async listActions(): Promise { + const actions = await this._listActions(); + return actions.map((action) => new ConneryAction(action, this)); + } + + /** + * Returns the specified Connery action wrapped as a LangChain Tool object. + * @param actionId The ID of the action to return. + * @returns A promise that resolves to a ConneryAction object. + */ + async getAction(actionId: string): Promise { + const action = await this._getAction(actionId); + return new ConneryAction(action, this); + } + + /** + * Runs the specified Connery action with the provided input. + * @param actionId The ID of the action to run. + * @param prompt This is a plain English prompt with all the information needed to run the action. + * @param input The input expected by the action. + * If provided together with the prompt, the input takes precedence over the input specified in the prompt. + * @returns A promise that resolves to a JSON string containing the output of the action. + */ + async runAction( + actionId: string, + prompt?: string, + input?: Input + ): Promise { + const result = await this._runAction(actionId, prompt, input); + return JSON.stringify(result); + } + + /** + * Returns the list of actions available in the Connery runner. + * @returns A promise that resolves to an array of Action objects. + */ + protected async _listActions(): Promise { + const response = await this.asyncCaller.call( + fetch, + `${this.runnerUrl}/v1/actions`, + { + method: "GET", + headers: this._getHeaders(), + } + ); + await this._handleError(response, "Failed to list actions"); + + const apiResponse: ApiResponse = await response.json(); + return apiResponse.data; + } + + /** + * Returns the specified action available in the Connery runner. + * @param actionId The ID of the action to return. + * @returns A promise that resolves to an Action object. + * @throws An error if the action with the specified ID is not found. + */ + protected async _getAction(actionId: string): Promise { + const actions = await this._listActions(); + const action = actions.find((a) => a.id === actionId); + if (!action) { + throw new Error( + `The action with ID "${actionId}" was not found in the list of available actions in the Connery runner.` + ); + } + return action; + } + + /** + * Runs the specified Connery action with the provided input. + * @param actionId The ID of the action to run. + * @param prompt This is a plain English prompt with all the information needed to run the action. + * @param input The input object expected by the action. + * If provided together with the prompt, the input takes precedence over the input specified in the prompt. + * @returns A promise that resolves to a RunActionResult object. + */ + protected async _runAction( + actionId: string, + prompt?: string, + input?: Input + ): Promise { + const response = await this.asyncCaller.call( + fetch, + `${this.runnerUrl}/v1/actions/${actionId}/run`, + { + method: "POST", + headers: this._getHeaders(), + body: JSON.stringify({ + prompt, + input, + }), + } + ); + await this._handleError(response, "Failed to run action"); + + const apiResponse: ApiResponse = await response.json(); + return apiResponse.data.output; + } + + /** + * Returns a standard set of HTTP headers to be used in API calls to the Connery runner. + * @returns An object containing the standard set of HTTP headers. + */ + protected _getHeaders(): Record { + return { + "Content-Type": "application/json", + "x-api-key": this.apiKey, + }; + } + + /** + * Shared error handler for API calls to the Connery runner. + * If the response is not ok, an error is thrown containing the error message returned by the Connery runner. + * Otherwise, the promise resolves to void. + * @param response The response object returned by the Connery runner. + * @param errorMessage The error message to be used in the error thrown if the response is not ok. + * @returns A promise that resolves to void. + * @throws An error containing the error message returned by the Connery runner. + */ + protected async _handleError( + response: Response, + errorMessage: string + ): Promise { + if (response.ok) return; + + const apiErrorResponse: ApiErrorResponse = await response.json(); + throw new Error( + `${errorMessage}. Status code: ${response.status}. Error message: ${apiErrorResponse.error.message}` + ); + } +} From 8154dbf9a3df4aca8a36dcf480c3251207738ac2 Mon Sep 17 00:00:00 2001 From: jacoblee93 Date: Mon, 4 Dec 2023 20:08:03 -0800 Subject: [PATCH 35/44] Bump core --- langchain/package.json | 2 +- yarn.lock | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/langchain/package.json b/langchain/package.json index b7635eed06e4..c005084768cf 100644 --- a/langchain/package.json +++ b/langchain/package.json @@ -1432,7 +1432,7 @@ }, "dependencies": { "@anthropic-ai/sdk": "^0.9.1", - "@langchain/core": "~0.0.6", + "@langchain/core": "~0.0.8", "binary-extensions": "^2.2.0", "expr-eval": "^2.0.2", "flat": "^5.0.2", diff --git a/yarn.lock b/yarn.lock index 72680a1f35c7..9a079463e64a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -8018,7 +8018,7 @@ __metadata: languageName: unknown linkType: soft -"@langchain/core@workspace:*, @langchain/core@workspace:langchain-core, @langchain/core@~0.0.6": +"@langchain/core@workspace:*, @langchain/core@workspace:langchain-core, @langchain/core@~0.0.8": version: 0.0.0-use.local resolution: "@langchain/core@workspace:langchain-core" dependencies: @@ -22650,7 +22650,7 @@ __metadata: "@gradientai/nodejs-sdk": ^1.2.0 "@huggingface/inference": ^2.6.4 "@jest/globals": ^29.5.0 - "@langchain/core": ~0.0.6 + "@langchain/core": ~0.0.8 "@mozilla/readability": ^0.4.4 "@notionhq/client": ^2.2.10 "@opensearch-project/opensearch": ^2.2.0 From a4c948a6f90d67a34866dcdd0cae90c12efdd6ab Mon Sep 17 00:00:00 2001 From: jacoblee93 Date: Mon, 4 Dec 2023 20:22:29 -0800 Subject: [PATCH 36/44] Release 0.0.201 --- langchain/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/langchain/package.json b/langchain/package.json index c005084768cf..ab967ae256ca 100644 --- a/langchain/package.json +++ b/langchain/package.json @@ -1,6 +1,6 @@ { "name": "langchain", - "version": "0.0.200", + "version": "0.0.201", "description": "Typescript bindings for langchain", "type": "module", "engines": { From b0b3139bec4edb39dcc050785598de9b6ffcc81c Mon Sep 17 00:00:00 2001 From: Jacob Lee Date: Mon, 4 Dec 2023 21:40:38 -0800 Subject: [PATCH 37/44] Fix OpenAI agent docs (#3543) --- .../agent_types/openai_functions_agent.mdx | 15 ++- .../agents/agent_types/openai_tools_agent.mdx | 4 +- .../src/agents/openai_runnable_with_memory.ts | 111 ++++++++++++++++++ 3 files changed, 123 insertions(+), 7 deletions(-) create mode 100644 examples/src/agents/openai_runnable_with_memory.ts diff --git a/docs/core_docs/docs/modules/agents/agent_types/openai_functions_agent.mdx b/docs/core_docs/docs/modules/agents/agent_types/openai_functions_agent.mdx index b55b35ab8f46..dc5dd77d6df0 100644 --- a/docs/core_docs/docs/modules/agents/agent_types/openai_functions_agent.mdx +++ b/docs/core_docs/docs/modules/agents/agent_types/openai_functions_agent.mdx @@ -55,10 +55,10 @@ Then, update your prompt to include another `MessagesPlaceholder`. This time we' ```typescript const prompt = ChatPromptTemplate.fromMessages([ - ["ai", "You are a helpful assistant"], + ["ai", "You are a helpful assistant."], + new MessagesPlaceholder("chat_history"), ["human", "{input}"], new MessagesPlaceholder("agent_scratchpad"), - new MessagesPlaceholder("chat_history"), ]); ``` @@ -77,9 +77,14 @@ const runnableAgent = RunnableSequence.from([ }, }, prompt, - modelWithTools, + modelWithFunctions, new OpenAIFunctionsAgentOutputParser(), ]); + +const executor = AgentExecutor.fromAgentAndTools({ + agent: runnableAgent, + tools, +}); ``` Finally we can call the agent, and save the output after the response is returned. @@ -122,8 +127,8 @@ console.log(result2); You may also inspect the LangSmith traces for both agent calls here: -- [Question 1](https://smith.langchain.com/public/12f4022d-f02f-4b00-9122-8493afe5507d/r) -- [Question 2](https://smith.langchain.com/public/2052d0d1-5640-44bc-9024-d443d46db2a4/r) +- [Question 1](https://smith.langchain.com/public/c1136951-f3f0-4ff5-a862-8db5d6bc8d04/r) +- [Question 2](https://smith.langchain.com/public/b536cdc0-9bc9-4bdf-9298-4d6d7f88556b/r) # With `initializeAgentExecutorWithOptions` diff --git a/docs/core_docs/docs/modules/agents/agent_types/openai_tools_agent.mdx b/docs/core_docs/docs/modules/agents/agent_types/openai_tools_agent.mdx index d999fb07b182..839ac958184d 100644 --- a/docs/core_docs/docs/modules/agents/agent_types/openai_tools_agent.mdx +++ b/docs/core_docs/docs/modules/agents/agent_types/openai_tools_agent.mdx @@ -65,10 +65,10 @@ Then, update your prompt to include another `MessagesPlaceholder`. This time we' ```typescript const prompt = ChatPromptTemplate.fromMessages([ - ["ai", "You are a helpful assistant"], + ["ai", "You are a helpful assistant."], + new MessagesPlaceholder("chat_history"), ["human", "{input}"], new MessagesPlaceholder("agent_scratchpad"), - new MessagesPlaceholder("chat_history"), ]); ``` diff --git a/examples/src/agents/openai_runnable_with_memory.ts b/examples/src/agents/openai_runnable_with_memory.ts new file mode 100644 index 000000000000..34b057a1c3fe --- /dev/null +++ b/examples/src/agents/openai_runnable_with_memory.ts @@ -0,0 +1,111 @@ +import { AgentExecutor } from "langchain/agents"; +import { ChatOpenAI } from "langchain/chat_models/openai"; +import { ChatPromptTemplate, MessagesPlaceholder } from "langchain/prompts"; +import { + AIMessage, + AgentStep, + BaseMessage, + FunctionMessage, +} from "langchain/schema"; +import { RunnableSequence } from "langchain/schema/runnable"; +import { SerpAPI, formatToOpenAIFunction } from "langchain/tools"; +import { Calculator } from "langchain/tools/calculator"; +import { OpenAIFunctionsAgentOutputParser } from "langchain/agents/openai/output_parser"; +import { BufferMemory } from "langchain/memory"; + +/** Define your list of tools. */ +const tools = [new Calculator(), new SerpAPI()]; +/** + * Define your chat model to use. + * In this example we'll use gpt-4 as it is much better + * at following directions in an agent than other models. + */ +const model = new ChatOpenAI({ modelName: "gpt-4", temperature: 0 }); + +/** + * Bind the tools to the LLM. + * Here we're using the `formatToOpenAIFunction` util function + * to format our tools into the proper schema for OpenAI functions. + */ +const modelWithFunctions = model.bind({ + functions: [...tools.map((tool) => formatToOpenAIFunction(tool))], +}); + +const memory = new BufferMemory({ + memoryKey: "history", // The object key to store the memory under + inputKey: "question", // The object key for the input + outputKey: "answer", // The object key for the output + returnMessages: true, +}); + +const prompt = ChatPromptTemplate.fromMessages([ + ["ai", "You are a helpful assistant."], + new MessagesPlaceholder("chat_history"), + ["human", "{input}"], + new MessagesPlaceholder("agent_scratchpad"), +]); + +const formatAgentSteps = (steps: AgentStep[]): BaseMessage[] => + steps.flatMap(({ action, observation }) => { + if ("messageLog" in action && action.messageLog !== undefined) { + const log = action.messageLog as BaseMessage[]; + return log.concat(new FunctionMessage(observation, action.tool)); + } else { + return [new AIMessage(action.log)]; + } + }); + +const runnableAgent = RunnableSequence.from([ + { + input: (i: { input: string; steps: AgentStep[] }) => i.input, + agent_scratchpad: (i: { input: string; steps: AgentStep[] }) => + formatAgentSteps(i.steps), + // Load memory here + chat_history: async (_: { input: string; steps: AgentStep[] }) => { + const { history } = await memory.loadMemoryVariables({}); + return history; + }, + }, + prompt, + modelWithFunctions, + new OpenAIFunctionsAgentOutputParser(), +]); + +const executor = AgentExecutor.fromAgentAndTools({ + agent: runnableAgent, + tools, +}); + +const query = "What is the weather in New York?"; +console.log(`Calling agent executor with query: ${query}`); +const result = await executor.invoke({ + input: query, +}); +console.log(result); +/* +Calling agent executor with query: What is the weather in New York? +{ + output: 'The current weather in New York is sunny with a temperature of 66 degrees Fahrenheit. The humidity is at 54% and the wind is blowing at 6 mph. There is 0% chance of precipitation.' +} +*/ + +// Save the result and initial input to memory +await memory.saveContext( + { + question: query, + }, + { + answer: result.output, + } +); + +const query2 = "Do I need a jacket?"; +const result2 = await executor.invoke({ + input: query2, +}); +console.log(result2); +/* +{ + output: 'Based on the current weather in New York, you may not need a jacket. However, if you feel cold easily or will be outside for a long time, you might want to bring a light jacket just in case.' +} + */ From dfd32715c44e9bc63f98bf5b8fd93dbd62b3320c Mon Sep 17 00:00:00 2001 From: Jacob Lee Date: Tue, 5 Dec 2023 09:04:49 -0800 Subject: [PATCH 38/44] Update contributing guidelines (#3550) --- CONTRIBUTING.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index bb3c21a172b6..8a01a608afe2 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -114,6 +114,14 @@ To get started, you will need to install the dependencies for the project. To do yarn ``` +Then, you will need to switch directories into `langchain-core` and build core by running: + +```bash +cd ../langchain-core +yarn +yarn build +``` + ### Linting We use [eslint](https://eslint.org/) to enforce standard lint rules. From a3f33a6f6a01ecf08a5f47fac5d850b08c658592 Mon Sep 17 00:00:00 2001 From: Brace Sproul Date: Tue, 5 Dec 2023 09:47:46 -0800 Subject: [PATCH 39/44] core[infra]: Adds turbo to core (#3551) --- docs/api_refs/package.json | 2 +- langchain-core/package.json | 4 +++- langchain-core/turbo.json | 23 +++++++++++++++++++++++ yarn.lock | 1 + 4 files changed, 28 insertions(+), 2 deletions(-) create mode 100644 langchain-core/turbo.json diff --git a/docs/api_refs/package.json b/docs/api_refs/package.json index c14730aabf79..63785f5673a5 100644 --- a/docs/api_refs/package.json +++ b/docs/api_refs/package.json @@ -6,7 +6,7 @@ "dev": "next dev -p 3001", "typedoc": "npx typedoc --options typedoc.json", "build:scripts": "node ./scripts/generate-api-refs.js && node ./scripts/update-typedoc-css.js", - "build": "yarn turbo run build:next", + "build": "yarn turbo run build --filter=@langchain/core --no-cache && yarn turbo run build:next", "build:next": "next build", "start": "yarn build && next start -p 3001", "lint": "next lint" diff --git a/langchain-core/package.json b/langchain-core/package.json index 09a6759cd513..00c5fa943c83 100644 --- a/langchain-core/package.json +++ b/langchain-core/package.json @@ -13,7 +13,8 @@ "url": "git@github.com:langchain-ai/langchainjs.git" }, "scripts": { - "build": "yarn clean && yarn build:esm && yarn build:cjs && yarn build:scripts", + "build": "yarn turbo run build:scripts", + "build:envs": "yarn build:esm && yarn build:cjs", "build:esm": "NODE_OPTIONS=--max-old-space-size=4096 tsc --outDir dist/ && rimraf dist/tests dist/**/tests", "build:cjs": "NODE_OPTIONS=--max-old-space-size=4096 tsc --outDir dist-cjs/ -p tsconfig.cjs.json && node scripts/move-cjs-to-dist.js && rimraf dist-cjs", "build:watch": "node scripts/create-entrypoints.js && tsc --outDir dist/ --watch", @@ -61,6 +62,7 @@ "prettier": "^2.8.3", "release-it": "^15.10.1", "rimraf": "^5.0.1", + "turbo": "latest", "typescript": "^5.0.0" }, "publishConfig": { diff --git a/langchain-core/turbo.json b/langchain-core/turbo.json new file mode 100644 index 000000000000..5fe0abbb4ad8 --- /dev/null +++ b/langchain-core/turbo.json @@ -0,0 +1,23 @@ +{ + "$schema": "https://turbo.build/schema.json", + "extends": ["//"], + "pipeline": { + "build:scripts": { + "outputs": [ + "tsconfig.json", + "package.json", + "**/*.js", + "**/*.d.ts", + "**/*.cjs", + "dist-cjs/**" + ], + "dependsOn": ["build:envs"] + }, + "build:envs": { + "dependsOn": ["clean"] + }, + "clean": { + "outputs": [".turbo/**", "dist/**"] + } + } +} diff --git a/yarn.lock b/yarn.lock index 9a079463e64a..ec00a845eef2 100644 --- a/yarn.lock +++ b/yarn.lock @@ -8045,6 +8045,7 @@ __metadata: prettier: ^2.8.3 release-it: ^15.10.1 rimraf: ^5.0.1 + turbo: latest typescript: ^5.0.0 uuid: ^9.0.0 zod: ^3.22.3 From 184ae6dedd560d4db72342852ca775bd0d15e311 Mon Sep 17 00:00:00 2001 From: Jacob Lee Date: Tue, 5 Dec 2023 10:15:29 -0800 Subject: [PATCH 40/44] docs[patch]: Agent pointer (#3549) * Adds pointer to OpenAI functions agent * Adds pointer to OpenAI functions agent --------- Co-authored-by: Brace Sproul --- .../docs/modules/agents/agent_types/structured_chat.mdx | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/docs/core_docs/docs/modules/agents/agent_types/structured_chat.mdx b/docs/core_docs/docs/modules/agents/agent_types/structured_chat.mdx index 8856d0b80178..0d2f7d784feb 100644 --- a/docs/core_docs/docs/modules/agents/agent_types/structured_chat.mdx +++ b/docs/core_docs/docs/modules/agents/agent_types/structured_chat.mdx @@ -1,5 +1,10 @@ # Structured tool chat +:::info +If you are using a functions-capable model like ChatOpenAI, we currently recommend that you use the [OpenAI Functions agent](/docs/modules/agents/agent_types/openai_functions_agent) +for more complex tool calling. +::: + The structured tool chat agent is capable of using multi-input tools. Older agents are configured to specify an action input as a single string, but this agent can use the provided tools' `args_schema` to populate the action input. From 752a145c284b905cfb4b39e9d2f43fc3c516f6a0 Mon Sep 17 00:00:00 2001 From: Brace Sproul Date: Tue, 5 Dec 2023 10:29:22 -0800 Subject: [PATCH 41/44] Revert "all[chore]: Use turbo repo to build api refs, docs and more" (#3535) * Revert "all[chore]: Use turbo repo to build api refs, docs and more (#3511)" This reverts commit f289f3d8b42d42988b04476edbb59bdd3fc1283f. * cr --- .github/workflows/test-exports.yml | 14 +-- docs/api_refs/package.json | 6 +- docs/api_refs/turbo.json | 14 --- docs/core_docs/package.json | 7 +- docs/core_docs/turbo.json | 13 --- langchain-core/src/callbacks/manager.ts | 4 +- .../src/language_models/chat_models.ts | 8 +- langchain-core/src/language_models/llms.ts | 13 +-- .../src/messages/tests/base_message.test.ts | 2 +- package.json | 8 +- turbo.json | 16 +-- yarn.lock | 108 +++++++++--------- 12 files changed, 80 insertions(+), 133 deletions(-) delete mode 100644 docs/api_refs/turbo.json delete mode 100644 docs/core_docs/turbo.json diff --git a/.github/workflows/test-exports.yml b/.github/workflows/test-exports.yml index 4872490161ff..15b15338107b 100644 --- a/.github/workflows/test-exports.yml +++ b/.github/workflows/test-exports.yml @@ -34,7 +34,7 @@ jobs: - name: Install dependencies run: yarn install --immutable - name: Build - run: yarn run build:deps && yarn build --filter=langchain + run: yarn run build:deps && yarn workspace langchain build shell: bash env: SKIP_API_DOCS: true @@ -54,7 +54,7 @@ jobs: - name: Install dependencies run: yarn install --immutable - name: Build - run: yarn run build:deps && yarn build --filter=langchain + run: yarn run build:deps && yarn workspace langchain build shell: bash env: SKIP_API_DOCS: true @@ -74,7 +74,7 @@ jobs: - name: Install dependencies run: yarn install --immutable - name: Build - run: yarn run build:deps && yarn build --filter=langchain + run: yarn run build:deps && yarn workspace langchain build shell: bash env: SKIP_API_DOCS: true @@ -94,7 +94,7 @@ jobs: - name: Install dependencies run: yarn install --immutable - name: Build - run: yarn run build:deps && yarn build --filter=langchain + run: yarn run build:deps && yarn workspace langchain build shell: bash env: SKIP_API_DOCS: true @@ -114,7 +114,7 @@ jobs: - name: Install dependencies run: yarn install --immutable - name: Build - run: yarn run build:deps && yarn build --filter=langchain + run: yarn run build:deps && yarn workspace langchain build shell: bash env: SKIP_API_DOCS: true @@ -134,7 +134,7 @@ jobs: - name: Install dependencies run: yarn install --immutable - name: Build - run: yarn run build:deps && yarn build --filter=langchain + run: yarn run build:deps && yarn workspace langchain build shell: bash env: SKIP_API_DOCS: true @@ -154,7 +154,7 @@ jobs: # - name: Install dependencies # run: yarn install --immutable # - name: Build - # run: yarn run build:deps && yarn build --filter=langchain + # run: yarn run build:deps && yarn workspace langchain build # shell: bash # env: # SKIP_API_DOCS: true diff --git a/docs/api_refs/package.json b/docs/api_refs/package.json index 63785f5673a5..262b3b25ec30 100644 --- a/docs/api_refs/package.json +++ b/docs/api_refs/package.json @@ -1,13 +1,12 @@ { - "name": "@langchain/api_refs", + "name": "api_refs", "version": "0.1.0", "private": true, "scripts": { "dev": "next dev -p 3001", "typedoc": "npx typedoc --options typedoc.json", "build:scripts": "node ./scripts/generate-api-refs.js && node ./scripts/update-typedoc-css.js", - "build": "yarn turbo run build --filter=@langchain/core --no-cache && yarn turbo run build:next", - "build:next": "next build", + "build": "yarn run build:deps && yarn workspace langchain build && yarn build:scripts && next build", "start": "yarn build && next start -p 3001", "lint": "next lint" }, @@ -26,7 +25,6 @@ "postcss": "^8", "tailwindcss": "^3.3.0", "ts-morph": "^20.0.0", - "turbo": "latest", "typescript": "^5" } } diff --git a/docs/api_refs/turbo.json b/docs/api_refs/turbo.json deleted file mode 100644 index 5d7266b7ed5e..000000000000 --- a/docs/api_refs/turbo.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "$schema": "https://turbo.build/schema.json", - "extends": ["//"], - "pipeline": { - "build:scripts": { - "outputs": ["public/**"], - "dependsOn": ["langchain#build"] - }, - "build:next": { - "outputs": [".next/**", ".vercel/**"], - "dependsOn": ["build:scripts"] - } - } -} diff --git a/docs/core_docs/package.json b/docs/core_docs/package.json index 10a299ddedab..bd7067f7ba8e 100644 --- a/docs/core_docs/package.json +++ b/docs/core_docs/package.json @@ -1,13 +1,11 @@ { - "name": "@langchain/core_docs", + "name": "core_docs", "version": "0.0.0", "private": true, "scripts": { "docusaurus": "docusaurus", "start": "yarn build:typedoc && rimraf ./docs/api && NODE_OPTIONS=--max-old-space-size=7168 docusaurus start", - "rimraf:build": "rimraf ./build", - "build:docusaurus": "NODE_OPTIONS=--max-old-space-size=7168 DOCUSAURUS_SSR_CONCURRENCY=4 docusaurus build", - "build": "yarn turbo run build:docusaurus", + "build": "yarn build:typedoc && rimraf ./build && NODE_OPTIONS=--max-old-space-size=7168 DOCUSAURUS_SSR_CONCURRENCY=4 docusaurus build", "build:typedoc": "cd ../api_refs && yarn build", "swizzle": "docusaurus swizzle", "deploy": "docusaurus deploy", @@ -48,7 +46,6 @@ "prettier": "^2.7.1", "rimraf": "^5.0.1", "swc-loader": "^0.2.3", - "turbo": "latest", "typedoc": "^0.24.4", "typedoc-plugin-markdown": "next" }, diff --git a/docs/core_docs/turbo.json b/docs/core_docs/turbo.json deleted file mode 100644 index 6f1c5b77b96a..000000000000 --- a/docs/core_docs/turbo.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "$schema": "https://turbo.build/schema.json", - "extends": ["//"], - "pipeline": { - "rimraf:build": { - "dependsOn": ["@langchain/api_refs#build"] - }, - "build:docusaurus": { - "outputs": [".docusaurus/**", "build/**"], - "dependsOn": ["rimraf:build"] - } - } -} diff --git a/langchain-core/src/callbacks/manager.ts b/langchain-core/src/callbacks/manager.ts index 572234de9660..baeccb445618 100644 --- a/langchain-core/src/callbacks/manager.ts +++ b/langchain-core/src/callbacks/manager.ts @@ -1,7 +1,7 @@ import { v4 as uuidv4 } from "uuid"; -import type { AgentAction, AgentFinish } from "../agents.js"; +import { AgentAction, AgentFinish } from "../agents.js"; import type { ChainValues } from "../utils/types.js"; -import type { LLMResult } from "../outputs.js"; +import { LLMResult } from "../outputs.js"; import { BaseCallbackHandler, CallbackHandlerMethods, diff --git a/langchain-core/src/language_models/chat_models.ts b/langchain-core/src/language_models/chat_models.ts index ac8d3d96d5e9..4a426c00b053 100644 --- a/langchain-core/src/language_models/chat_models.ts +++ b/langchain-core/src/language_models/chat_models.ts @@ -2,17 +2,17 @@ import { AIMessage, BaseMessage, BaseMessageChunk, - type BaseMessageLike, + BaseMessageLike, HumanMessage, coerceMessageLikeToMessage, } from "../messages/index.js"; import { BasePromptValue } from "../prompt_values.js"; import { - type LLMResult, + LLMResult, RUN_KEY, - type ChatGeneration, + ChatGeneration, ChatGenerationChunk, - type ChatResult, + ChatResult, } from "../outputs.js"; import { BaseLanguageModel, diff --git a/langchain-core/src/language_models/llms.ts b/langchain-core/src/language_models/llms.ts index 2bbccea20edc..c5ccb205cc7d 100644 --- a/langchain-core/src/language_models/llms.ts +++ b/langchain-core/src/language_models/llms.ts @@ -1,16 +1,11 @@ import { AIMessage, BaseMessage, getBufferString } from "../messages/index.js"; import { BasePromptValue } from "../prompt_values.js"; +import { LLMResult, RUN_KEY, Generation, GenerationChunk } from "../outputs.js"; import { - type LLMResult, - RUN_KEY, - type Generation, - GenerationChunk, -} from "../outputs.js"; -import { - type BaseCallbackConfig, + BaseCallbackConfig, CallbackManager, CallbackManagerForLLMRun, - type Callbacks, + Callbacks, } from "../callbacks/manager.js"; import { BaseLanguageModel, @@ -18,7 +13,7 @@ import { type BaseLanguageModelInput, type BaseLanguageModelParams, } from "./base.js"; -import type { RunnableConfig } from "../runnables/config.js"; +import { RunnableConfig } from "../runnables/config.js"; export type SerializedLLM = { _model: string; diff --git a/langchain-core/src/messages/tests/base_message.test.ts b/langchain-core/src/messages/tests/base_message.test.ts index c7b9d2908c41..0e05d2b00bc0 100644 --- a/langchain-core/src/messages/tests/base_message.test.ts +++ b/langchain-core/src/messages/tests/base_message.test.ts @@ -1,6 +1,6 @@ import { test } from "@jest/globals"; import { ChatPromptTemplate } from "../../prompts/chat.js"; -import { HumanMessage } from "../index.js"; +import { HumanMessage } from "../../messages/index.js"; test("Test ChatPromptTemplate can format OpenAI content image messages", async () => { const message = new HumanMessage({ diff --git a/package.json b/package.json index b821d01e3a71..af0779770ea1 100644 --- a/package.json +++ b/package.json @@ -19,12 +19,12 @@ "packageManager": "yarn@3.4.1", "scripts": { "build": "turbo run build --filter=\"!test-exports-*\" --concurrency 1", - "build:deps": "yarn build --filter=@langchain/openai --filter=@langchain/anthropic --filter=@langchain/core", + "build:deps": "yarn workspace @langchain/core build && yarn workspace @langchain/anthropic build && yarn workspace @langchain/openai build", "format": "turbo run format", "format:check": "turbo run format:check", "lint": "turbo run lint --concurrency 1", "lint:fix": "yarn lint -- --fix", - "test": "yarn test:unit && yarn build --filter=langchain && yarn test:exports:docker", + "test": "yarn test:unit && yarn workspace @langchain/core build && yarn workspace langchain build && yarn test:exports:docker", "test:unit": "turbo run test --filter @langchain/core --filter langchain", "test:int": "yarn run test:int:deps && turbo run test:integration ; yarn run test:int:deps:down", "test:int:deps": "docker compose -f test-int-deps-docker-compose.yml up -d", @@ -34,8 +34,8 @@ "publish:core": "bash langchain/scripts/release-branch.sh && turbo run --filter @langchain/core build lint test --concurrency 1 && yarn run test:exports:docker && yarn workspace @langchain/core run release && echo '🔗 Open https://github.com/langchain-ai/langchainjs/compare/release?expand=1 and merge the release PR'", "example": "yarn workspace examples start", "precommit": "turbo run precommit", - "docs": "yarn workspace @langchain/core_docs start", - "docs:api_refs": "yarn workspace @langchain/api_refs start" + "docs": "yarn workspace core_docs start", + "docs:api_refs": "yarn workspace api_refs start" }, "author": "LangChain", "license": "MIT", diff --git a/turbo.json b/turbo.json index 1324193d7ff9..01a50e732e26 100644 --- a/turbo.json +++ b/turbo.json @@ -2,21 +2,7 @@ "$schema": "https://turbo.build/schema.json", "globalDependencies": ["**/.env"], "pipeline": { - "@langchain/core#build": { - "outputs": [ - "langchain-core/dist/**", - "langchain-core/dist-cjs/**", - "langchain-core/*.js", - "langchain-core/*.cjs", - "langchain-core/*.d.ts" - ], - "inputs": [ - "langchain-core/src/**", - "langchain-core/scripts/**", - "langchain-core/package.json", - "langchain-core/tsconfig.json" - ] - }, + "@langchain/core#build": {}, "libs/langchain-anthropic#build": { "dependsOn": ["@langchain/core#build"] }, diff --git a/yarn.lock b/yarn.lock index ec00a845eef2..9015e889a3c3 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7997,27 +7997,6 @@ __metadata: languageName: unknown linkType: soft -"@langchain/api_refs@workspace:docs/api_refs": - version: 0.0.0-use.local - resolution: "@langchain/api_refs@workspace:docs/api_refs" - dependencies: - "@types/node": ^20 - "@types/react": ^18 - "@types/react-dom": ^18 - autoprefixer: ^10.0.1 - eslint: ^8 - eslint-config-next: 14.0.1 - next: 14.0.1 - postcss: ^8 - react: ^18 - react-dom: ^18 - tailwindcss: ^3.3.0 - ts-morph: ^20.0.0 - turbo: latest - typescript: ^5 - languageName: unknown - linkType: soft - "@langchain/core@workspace:*, @langchain/core@workspace:langchain-core, @langchain/core@~0.0.8": version: 0.0.0-use.local resolution: "@langchain/core@workspace:langchain-core" @@ -8052,40 +8031,6 @@ __metadata: languageName: unknown linkType: soft -"@langchain/core_docs@workspace:docs/core_docs": - version: 0.0.0-use.local - resolution: "@langchain/core_docs@workspace:docs/core_docs" - dependencies: - "@babel/eslint-parser": ^7.18.2 - "@docusaurus/core": 2.4.3 - "@docusaurus/preset-classic": 2.4.3 - "@docusaurus/remark-plugin-npm2yarn": 2.4.3 - "@mdx-js/react": ^1.6.22 - "@swc/core": ^1.3.62 - clsx: ^1.2.1 - docusaurus-plugin-typedoc: 1.0.0-next.5 - eslint: ^8.19.0 - eslint-config-airbnb: ^19.0.4 - eslint-config-prettier: ^8.5.0 - eslint-plugin-header: ^3.1.1 - eslint-plugin-import: ^2.26.0 - eslint-plugin-jsx-a11y: ^6.6.0 - eslint-plugin-react: ^7.30.1 - eslint-plugin-react-hooks: ^4.6.0 - json-loader: ^0.5.7 - prettier: ^2.7.1 - process: ^0.11.10 - react: ^17.0.2 - react-dom: ^17.0.2 - rimraf: ^5.0.1 - swc-loader: ^0.2.3 - turbo: latest - typedoc: ^0.24.4 - typedoc-plugin-markdown: next - webpack: ^5.75.0 - languageName: unknown - linkType: soft - "@langchain/openai@workspace:libs/langchain-openai": version: 0.0.0-use.local resolution: "@langchain/openai@workspace:libs/langchain-openai" @@ -13142,6 +13087,26 @@ __metadata: languageName: node linkType: hard +"api_refs@workspace:docs/api_refs": + version: 0.0.0-use.local + resolution: "api_refs@workspace:docs/api_refs" + dependencies: + "@types/node": ^20 + "@types/react": ^18 + "@types/react-dom": ^18 + autoprefixer: ^10.0.1 + eslint: ^8 + eslint-config-next: 14.0.1 + next: 14.0.1 + postcss: ^8 + react: ^18 + react-dom: ^18 + tailwindcss: ^3.3.0 + ts-morph: ^20.0.0 + typescript: ^5 + languageName: unknown + linkType: soft + "apify-client@npm:^2.7.1": version: 2.7.1 resolution: "apify-client@npm:2.7.1" @@ -15498,6 +15463,39 @@ __metadata: languageName: node linkType: hard +"core_docs@workspace:docs/core_docs": + version: 0.0.0-use.local + resolution: "core_docs@workspace:docs/core_docs" + dependencies: + "@babel/eslint-parser": ^7.18.2 + "@docusaurus/core": 2.4.3 + "@docusaurus/preset-classic": 2.4.3 + "@docusaurus/remark-plugin-npm2yarn": 2.4.3 + "@mdx-js/react": ^1.6.22 + "@swc/core": ^1.3.62 + clsx: ^1.2.1 + docusaurus-plugin-typedoc: 1.0.0-next.5 + eslint: ^8.19.0 + eslint-config-airbnb: ^19.0.4 + eslint-config-prettier: ^8.5.0 + eslint-plugin-header: ^3.1.1 + eslint-plugin-import: ^2.26.0 + eslint-plugin-jsx-a11y: ^6.6.0 + eslint-plugin-react: ^7.30.1 + eslint-plugin-react-hooks: ^4.6.0 + json-loader: ^0.5.7 + prettier: ^2.7.1 + process: ^0.11.10 + react: ^17.0.2 + react-dom: ^17.0.2 + rimraf: ^5.0.1 + swc-loader: ^0.2.3 + typedoc: ^0.24.4 + typedoc-plugin-markdown: next + webpack: ^5.75.0 + languageName: unknown + linkType: soft + "cosmiconfig@npm:8.0.0": version: 8.0.0 resolution: "cosmiconfig@npm:8.0.0" From df6f043d2cfe119395ba1e3e3b0f4ff3a49945e8 Mon Sep 17 00:00:00 2001 From: Tudor Golubenco Date: Tue, 5 Dec 2023 23:29:20 +0200 Subject: [PATCH 42/44] Upgrade xata client to 0.28.0 and apply required change (#3553) Co-authored-by: jacoblee93 --- .../docs/integrations/vectorstores/xata.mdx | 2 +- langchain/package.json | 4 ++-- langchain/src/vectorstores/tests/xata.int.test.ts | 5 +++++ langchain/src/vectorstores/xata.ts | 2 +- yarn.lock | 13 +++++++++++-- 5 files changed, 20 insertions(+), 6 deletions(-) diff --git a/docs/core_docs/docs/integrations/vectorstores/xata.mdx b/docs/core_docs/docs/integrations/vectorstores/xata.mdx index bc302ff0a8b7..604a67d824ae 100644 --- a/docs/core_docs/docs/integrations/vectorstores/xata.mdx +++ b/docs/core_docs/docs/integrations/vectorstores/xata.mdx @@ -17,7 +17,7 @@ npm install @xata.io/cli -g In the [Xata UI](https://app.xata.io) create a new database. You can name it whatever you want, but for this example we'll use `langchain`. Create a table, again you can name it anything, but we will use `vectors`. Add the following columns via the UI: -- `content` of type "Long text". This is used to store the `Document.pageContent` values. +- `content` of type "Text". This is used to store the `Document.pageContent` values. - `embedding` of type "Vector". Use the dimension used by the model you plan to use (1536 for OpenAI). - any other columns you want to use as metadata. They are populated from the `Document.metadata` object. For example, if in the `Document.metadata` object you have a `title` property, you can create a `title` column in the table and it will be populated. diff --git a/langchain/package.json b/langchain/package.json index ab967ae256ca..8f1777db616d 100644 --- a/langchain/package.json +++ b/langchain/package.json @@ -944,7 +944,7 @@ "@vercel/kv": "^0.2.3", "@vercel/postgres": "^0.5.0", "@writerai/writer-sdk": "^0.40.2", - "@xata.io/client": "^0.25.1", + "@xata.io/client": "^0.28.0", "@xenova/transformers": "^2.5.4", "@zilliz/milvus2-sdk-node": ">=2.2.11", "apify-client": "^2.7.1", @@ -1066,7 +1066,7 @@ "@vercel/kv": "^0.2.3", "@vercel/postgres": "^0.5.0", "@writerai/writer-sdk": "^0.40.2", - "@xata.io/client": "^0.25.1", + "@xata.io/client": "^0.28.0", "@xenova/transformers": "^2.5.4", "@zilliz/milvus2-sdk-node": ">=2.2.7", "apify-client": "^2.7.1", diff --git a/langchain/src/vectorstores/tests/xata.int.test.ts b/langchain/src/vectorstores/tests/xata.int.test.ts index cab301657669..977754d0189c 100644 --- a/langchain/src/vectorstores/tests/xata.int.test.ts +++ b/langchain/src/vectorstores/tests/xata.int.test.ts @@ -6,6 +6,11 @@ import { XataVectorSearch } from "../xata.js"; import { OpenAIEmbeddings } from "../../embeddings/openai.js"; import { Document } from "../../document.js"; +// Tests require a DB with a table called "docs" with: +// * a column name content of type Text +// * a column named embedding of type Vector +// * a column named a of type Integer + test.skip("XataVectorSearch integration", async () => { if (!process.env.XATA_API_KEY) { throw new Error("XATA_API_KEY not set"); diff --git a/langchain/src/vectorstores/xata.ts b/langchain/src/vectorstores/xata.ts index 8d8ed0b5c7e9..ccd6089ea4e9 100644 --- a/langchain/src/vectorstores/xata.ts +++ b/langchain/src/vectorstores/xata.ts @@ -118,7 +118,7 @@ export class XataVectorSearch< k: number, filter?: XataFilter | undefined ): Promise<[Document, number][]> { - const records = await this.client.db[this.table].vectorSearch( + const { records } = await this.client.db[this.table].vectorSearch( "embedding", query, { diff --git a/yarn.lock b/yarn.lock index 9015e889a3c3..1d547abb963e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -12665,6 +12665,15 @@ __metadata: languageName: node linkType: hard +"@xata.io/client@npm:^0.28.0": + version: 0.28.0 + resolution: "@xata.io/client@npm:0.28.0" + peerDependencies: + typescript: ">=4.5" + checksum: 39dbf590c320ba49623f44ab1124fb084f08f2820d04bc1f7c20459304ac4008227964ce4e5d9bf1ab34828c7d3fa3afb50f3455a1f37a981238463ae4289036 + languageName: node + linkType: hard + "@xenova/transformers@npm:^2.5.4": version: 2.5.4 resolution: "@xenova/transformers@npm:2.5.4" @@ -22690,7 +22699,7 @@ __metadata: "@vercel/kv": ^0.2.3 "@vercel/postgres": ^0.5.0 "@writerai/writer-sdk": ^0.40.2 - "@xata.io/client": ^0.25.1 + "@xata.io/client": ^0.28.0 "@xenova/transformers": ^2.5.4 "@zilliz/milvus2-sdk-node": ">=2.2.11" apify-client: ^2.7.1 @@ -22827,7 +22836,7 @@ __metadata: "@vercel/kv": ^0.2.3 "@vercel/postgres": ^0.5.0 "@writerai/writer-sdk": ^0.40.2 - "@xata.io/client": ^0.25.1 + "@xata.io/client": ^0.28.0 "@xenova/transformers": ^2.5.4 "@zilliz/milvus2-sdk-node": ">=2.2.7" apify-client: ^2.7.1 From f0d7ff03899ed1b785f94ea6f4e7c774d8308dfa Mon Sep 17 00:00:00 2001 From: Brace Sproul Date: Tue, 5 Dec 2023 14:13:19 -0800 Subject: [PATCH 43/44] core[docs]: Docs for with listeners runnable method (#3531) * core[docs]: Docs for with listeners runnable method * chore: lint files * cr --- .../callbacks/how_to/with_listeners.mdx | 19 ++++++ .../expression_language/with_listeners.ts | 59 +++++++++++++++++++ 2 files changed, 78 insertions(+) create mode 100644 docs/core_docs/docs/modules/callbacks/how_to/with_listeners.mdx create mode 100644 examples/src/guides/expression_language/with_listeners.ts diff --git a/docs/core_docs/docs/modules/callbacks/how_to/with_listeners.mdx b/docs/core_docs/docs/modules/callbacks/how_to/with_listeners.mdx new file mode 100644 index 000000000000..7a7821e1719b --- /dev/null +++ b/docs/core_docs/docs/modules/callbacks/how_to/with_listeners.mdx @@ -0,0 +1,19 @@ +import CodeBlock from "@theme/CodeBlock"; +import Example from "@examples/guides/expression_language/with_listeners.ts"; + +# Listeners + +LangChain callbacks offer a method `withListeners` which allow you to add event listeners to the following events: + +- `onStart` - called when the chain starts +- `onEnd` - called when the chain ends +- `onError` - called when an error occurs + +These methods accept a callback function which will be called when the event occurs. The callback function can accept two arguments: + +- `input` - the input value, for example it would be `RunInput` if used with a Runnable. +- `config` - an optional config object. This can contain metadata, callbacks or any other values passed in as a config object when the chain is started. + +Below is an example which demonstrates how to use the `withListeners` method: + +{Example} diff --git a/examples/src/guides/expression_language/with_listeners.ts b/examples/src/guides/expression_language/with_listeners.ts new file mode 100644 index 000000000000..d7054caaca95 --- /dev/null +++ b/examples/src/guides/expression_language/with_listeners.ts @@ -0,0 +1,59 @@ +import { Run } from "langchain/callbacks"; +import { ChatOpenAI } from "langchain/chat_models/openai"; +import { ChatPromptTemplate } from "langchain/prompts"; + +const prompt = ChatPromptTemplate.fromMessages([ + ["ai", "You are a nice assistant."], + ["human", "{question}"], +]); +const model = new ChatOpenAI({}); +const chain = prompt.pipe(model); + +const trackTime = () => { + let start: { startTime: number; question: string }; + let end: { endTime: number; answer: string }; + + const handleStart = (run: Run) => { + start = { + startTime: run.start_time, + question: run.inputs.question, + }; + }; + + const handleEnd = (run: Run) => { + if (run.end_time && run.outputs) { + end = { + endTime: run.end_time, + answer: run.outputs.content, + }; + } + + console.log("start", start); + console.log("end", end); + console.log(`total time: ${end.endTime - start.startTime}ms`); + }; + + return { handleStart, handleEnd }; +}; + +const { handleStart, handleEnd } = trackTime(); + +await chain + .withListeners({ + onStart: (run: Run) => { + handleStart(run); + }, + onEnd: (run: Run) => { + handleEnd(run); + }, + }) + .invoke({ question: "What is the meaning of life?" }); + +/** + * start { startTime: 1701723365470, question: 'What is the meaning of life?' } +end { + endTime: 1701723368767, + answer: "The meaning of life is a philosophical question that has been contemplated and debated by scholars, philosophers, and individuals for centuries. The answer to this question can vary depending on one's beliefs, perspectives, and values. Some suggest that the meaning of life is to seek happiness and fulfillment, others propose it is to serve a greater purpose or contribute to the well-being of others. Ultimately, the meaning of life can be subjective and personal, and it is up to each individual to determine their own sense of purpose and meaning." +} +total time: 3297ms + */ From 1e2bfb7c2aeaf86ca41fbce419904e98d7eea3ce Mon Sep 17 00:00:00 2001 From: Brace Sproul Date: Tue, 5 Dec 2023 14:33:42 -0800 Subject: [PATCH 44/44] langchain[docs]: agent stream example docs (#3384) * agent stream example docs * cr * Fixed agent * chore: lint files * Added extra chat message class to types for history * cr * cr * cr * cr * cr * chore: yarn prettier * cr --- .../docs/modules/agents/how_to/streaming.mdx | 15 ++ .../docs/modules/callbacks/index.mdx | 6 +- examples/src/agents/stream.ts | 128 ++++++++++++++++++ 3 files changed, 148 insertions(+), 1 deletion(-) create mode 100644 docs/core_docs/docs/modules/agents/how_to/streaming.mdx create mode 100644 examples/src/agents/stream.ts diff --git a/docs/core_docs/docs/modules/agents/how_to/streaming.mdx b/docs/core_docs/docs/modules/agents/how_to/streaming.mdx new file mode 100644 index 000000000000..d5ca8ac3ba5c --- /dev/null +++ b/docs/core_docs/docs/modules/agents/how_to/streaming.mdx @@ -0,0 +1,15 @@ +import CodeBlock from "@theme/CodeBlock"; +import StreamingExample from "@examples/agents/stream.ts"; + +# Streaming + +Agents have the ability to stream iterations and actions back while they're still working. +This can be very useful for any realtime application where you have users who need insights on the agent's progress while is has yet to finish. + +Setting up streaming with agents is very simple, even with existing agents. The only change required is switching your `executor.invoke({})` to be `executor.stream({})`. + +Below is a simple example of streaming with an agent. + +You can find the [LangSmith](https://smith.langchain.com/) trace for this example by clicking [here](https://smith.langchain.com/public/08978fa7-bb99-427b-850e-35773cae1453/r). + +{StreamingExample} diff --git a/docs/core_docs/docs/modules/callbacks/index.mdx b/docs/core_docs/docs/modules/callbacks/index.mdx index 9054fc92a6a4..f1451ba96fbc 100644 --- a/docs/core_docs/docs/modules/callbacks/index.mdx +++ b/docs/core_docs/docs/modules/callbacks/index.mdx @@ -63,7 +63,11 @@ import StreamingExample from "@examples/models/llm/llm_streaming.ts"; ### Multiple handlers -We offer a method on the `CallbackManager` class that allows you to create a one-off handler. This is useful if eg. you need to create a handler that you will use only for a single request, eg to stream the output of an LLM/Agent/etc to a websocket. +We offer a method on the `CallbackManager` class that allows you to create a one-off handler. This is useful if eg. you need to create a handler that you will use only for a single request. + +:::tip +Agents now have built in streaming support! Click [here](/docs/modules/agents/how_to/streaming) for more details. +::: This is a more complete example that passes a `CallbackManager` to a ChatModel, and LLMChain, a Tool, and an Agent. diff --git a/examples/src/agents/stream.ts b/examples/src/agents/stream.ts new file mode 100644 index 000000000000..52e4726c8652 --- /dev/null +++ b/examples/src/agents/stream.ts @@ -0,0 +1,128 @@ +import { AgentExecutor, ZeroShotAgent } from "langchain/agents"; +import { formatLogToString } from "langchain/agents/format_scratchpad/log"; +import { ChatOpenAI } from "langchain/chat_models/openai"; +import { OpenAIEmbeddings } from "langchain/embeddings/openai"; +import { BufferMemory } from "langchain/memory"; +import { ChatPromptTemplate } from "langchain/prompts"; +import { RunnableSequence } from "langchain/runnables"; +import { Tool } from "langchain/tools"; +import { Calculator } from "langchain/tools/calculator"; +import { WebBrowser } from "langchain/tools/webbrowser"; + +// Initialize the LLM chat model to use in the agent. +const model = new ChatOpenAI({ + temperature: 0, + modelName: "gpt-4-1106-preview", +}); +// Define the tools the agent will have access to. +const tools = [ + new WebBrowser({ model, embeddings: new OpenAIEmbeddings() }), + new Calculator(), +]; +// Craft your agent's prompt. It's important to include the following parts: +// 1. tools -> This is the name and description of each tool the agent has access to. +// Remember to separate each tool with a new line. +// +// 2. toolNames -> Reiterate the names of the tools in the middle of the prompt +// after explaining how to format steps, etc. +// +// 3. intermediateSteps -> This is the history of the agent's thought process. +// This is very important because without this the agent will have zero context +// on past actions and observations. +const prompt = ChatPromptTemplate.fromMessages([ + [ + "ai", + `Answer the following questions as best you can. You have access to the following tools: + +{tools} + +Use the following format in your response: + +Question: the input question you must answer +Thought: you should always think about what to do +Action: the action to take, should be one of [{toolNames}] +Action Input: the input to the action +Observation: the result of the action +... (this Thought/Action/Action Input/Observation can repeat N times) +Thought: I now know the final answer +Final Answer: the final answer to the original input question + +Begin! + +History: +{intermediateSteps} + +Question: {question} +Thought:`, + ], +]); + +// Initialize the memory buffer. This is where our past steps will be stored. +const memory = new BufferMemory({}); +// Use the default output parser for the agent. This is a class which parses +// the string responses from the LLM into AgentStep's or AgentFinish. +const outputParser = ZeroShotAgent.getDefaultOutputParser(); +// The initial input which we'll pass to the agent. Note the inclusion +// of the tools array we defined above. +const input = { + question: `What is the word of the day on merriam webster`, + tools, +}; +// Create the runnable which will be responsible for executing agent steps. +const runnable = RunnableSequence.from([ + { + toolNames: (i: { tools: Array; question: string }) => + i.tools.map((t) => t.name).join(", "), + tools: (i: { tools: Array; question: string }) => + i.tools.map((t) => `${t.name}: ${t.description}`).join("\n"), + question: (i: { tools: Array; question: string }) => i.question, + intermediateSteps: async (_: { tools: Array; question: string }) => { + const { history } = await memory.loadMemoryVariables({}); + return history.replaceAll("Human: none", ""); + }, + }, + prompt, + model, + outputParser, +]); +// Initialize the AgentExecutor with the runnable defined above, and the +// tools array. +const executor = AgentExecutor.fromAgentAndTools({ + agent: runnable, + tools, +}); +// Define a custom function which will format the agent steps to a string, +// then save to memory. +const saveMemory = async (output: any) => { + if (!("intermediateSteps" in output)) return; + const { intermediateSteps } = output; + await memory.saveContext( + { human: "none" }, + { + history: formatLogToString(intermediateSteps), + } + ); +}; + +console.log("Loaded agent."); + +console.log(`Executing with question "${input.question}"...`); + +// Call `.stream()` with the inputs on the executor, then +// iterate over the steam and save each stream step to memory. +const result = await executor.stream(input); +// eslint-disable-next-line @typescript-eslint/no-explicit-any +const finalResponse: Array = []; +for await (const item of result) { + console.log("Stream item:", { + ...item, + }); + await saveMemory(item); + finalResponse.push(item); +} +console.log("Final response:", finalResponse); + +/** + * See the LangSmith trace for this agent example here: + * @link https://smith.langchain.com/public/08978fa7-bb99-427b-850e-35773cae1453/r + */