From 8bf9bafd58bbae83c659d244ab2bae5f609a61ec Mon Sep 17 00:00:00 2001 From: jacoblee93 Date: Tue, 13 Aug 2024 00:54:36 -0700 Subject: [PATCH 001/344] Start Mistral 1.0.0 migration --- libs/langchain-mistralai/package.json | 2 +- libs/langchain-mistralai/src/chat_models.ts | 71 +++++++++++---------- yarn.lock | 11 +++- 3 files changed, 49 insertions(+), 35 deletions(-) diff --git a/libs/langchain-mistralai/package.json b/libs/langchain-mistralai/package.json index 4bd9bd8a7b91..3095517a2fe3 100644 --- a/libs/langchain-mistralai/package.json +++ b/libs/langchain-mistralai/package.json @@ -36,7 +36,7 @@ "license": "MIT", "dependencies": { "@langchain/core": ">=0.2.21 <0.3.0", - "@mistralai/mistralai": "^0.4.0", + "@mistralai/mistralai": "^1.0.2", "uuid": "^10.0.0", "zod": "^3.22.4", "zod-to-json-schema": "^3.22.4" diff --git a/libs/langchain-mistralai/src/chat_models.ts b/libs/langchain-mistralai/src/chat_models.ts index b8d1a6348f70..72f60c8b79b4 100644 --- a/libs/langchain-mistralai/src/chat_models.ts +++ b/libs/langchain-mistralai/src/chat_models.ts @@ -1,14 +1,15 @@ import { v4 as uuidv4 } from "uuid"; import { - ChatCompletionResponse, - Function as MistralAIFunction, - ToolCalls as MistralAIToolCalls, - ResponseFormat, - ChatCompletionResponseChunk, - ChatRequest, - Tool as MistralAITool, - Message as MistralAIMessage, - TokenUsage as MistralAITokenUsage, + Mistral as MistralClient, + // MistralChatCompletionResponse, + // Function as MistralAIFunction, + // ToolCalls as MistralAIToolCalls, + // ResponseFormat, + // MistralChatCompletionResponseChunk, + // ChatRequest, + // Tool as MistralAITool, + // Message as MistralAIMessage, + // TokenUsage as MistralAITokenUsage, } from "@mistralai/mistralai"; import { MessageType, @@ -75,6 +76,18 @@ interface TokenUsage { export type MistralAIToolChoice = "auto" | "any" | "none"; +export type MistralChatCompletionParams = Parameters[0]; +export type MistralAIToolCall = NonNullable[number] + +export type MistralChatCompletionResponse = Awaited>; +export type MistralChatCompletionResponseChunk = Awaited>; +export type MistralChatCompletionChoice = NonNullable[number]; + +export type MistralAITool = NonNullable[number]; +export type MistralAIFunction = MistralAITool["function"]; + +export type MistralAIMessage = NonNullable; + type MistralAIToolInput = { type: string; function: MistralAIFunction }; type ChatMistralAIToolType = @@ -200,14 +213,14 @@ function convertMessagesToMistralMessages( ); }; - const getTools = (message: BaseMessage): MistralAIToolCalls[] | undefined => { + const getTools = (message: BaseMessage): MistralAIToolCall[] | undefined => { if (isAIMessage(message) && !!message.tool_calls?.length) { return message.tool_calls .map((toolCall) => ({ ...toolCall, id: _convertToolCallIdToMistralCompatible(toolCall.id ?? ""), })) - .map(convertLangChainToolCallToOpenAI) as MistralAIToolCalls[]; + .map(convertLangChainToolCallToOpenAI) as MistralAIToolCall[]; } if (!message.additional_kwargs.tool_calls?.length) { return undefined; @@ -244,16 +257,16 @@ function convertMessagesToMistralMessages( } function mistralAIResponseToChatMessage( - choice: ChatCompletionResponse["choices"][0], + choice: MistralChatCompletionResponse["choices"][0], usage?: MistralAITokenUsage ): BaseMessage { const { message } = choice; // MistralAI SDK does not include tool_calls in the non // streaming return type, so we need to extract it like this // to satisfy typescript. - let rawToolCalls: MistralAIToolCalls[] = []; + let rawToolCalls: MistralAIToolCall[] = []; if ("tool_calls" in message && Array.isArray(message.tool_calls)) { - rawToolCalls = message.tool_calls as MistralAIToolCalls[]; + rawToolCalls = message.tool_calls as MistralAIToolCall[]; } switch (message.role) { case "assistant": { @@ -301,7 +314,7 @@ function _convertDeltaToMessageChunk( delta: { role?: string | undefined; content?: string | undefined; - tool_calls?: MistralAIToolCalls[] | undefined; + tool_calls?: MistralAIToolCall[] | undefined; }, usage?: MistralAITokenUsage | null ) { @@ -791,7 +804,6 @@ export class ChatMistralAI< this.temperature = fields?.temperature ?? this.temperature; this.topP = fields?.topP ?? this.topP; this.maxTokens = fields?.maxTokens ?? this.maxTokens; - this.safeMode = fields?.safeMode ?? this.safeMode; this.safePrompt = fields?.safePrompt ?? this.safePrompt; this.randomSeed = fields?.seed ?? fields?.randomSeed ?? this.seed; this.seed = this.randomSeed; @@ -820,12 +832,12 @@ export class ChatMistralAI< */ invocationParams( options?: this["ParsedCallOptions"] - ): Omit { + ): Omit { const { response_format, tools, tool_choice } = options ?? {}; const mistralAITools: Array | undefined = tools?.length ? _convertToolToMistralTool(tools) : undefined; - const params: Omit = { + const params: Omit = { model: this.model, tools: mistralAITools, temperature: this.temperature, @@ -858,31 +870,30 @@ export class ChatMistralAI< async completionWithRetry( input: ChatRequest, streaming: true - ): Promise>; + ): Promise>; async completionWithRetry( input: ChatRequest, streaming: false - ): Promise; + ): Promise; async completionWithRetry( input: ChatRequest, streaming: boolean ): Promise< - ChatCompletionResponse | AsyncGenerator + MistralChatCompletionResponse | AsyncGenerator > { - const { MistralClient } = await this.imports(); - const client = new MistralClient(this.apiKey, this.endpoint); + const client = new MistralClient({ apiKey: this.apiKey, serverURL: this.endpoint }); return this.caller.call(async () => { try { let res: - | ChatCompletionResponse - | AsyncGenerator; + | MistralChatCompletionResponse + | AsyncGenerator; if (streaming) { - res = client.chatStream(input); + res = client.chat.stream(input); } else { - res = await client.chat(input); + res = await client.chat.complete(input); } return res; // eslint-disable-next-line @typescript-eslint/no-explicit-any @@ -1191,12 +1202,6 @@ export class ChatMistralAI< parsedWithFallback, ]); } - - /** @ignore */ - private async imports() { - const { default: MistralClient } = await import("@mistralai/mistralai"); - return { MistralClient }; - } } function isZodSchema< diff --git a/yarn.lock b/yarn.lock index 3bfd7db12c29..2114b12d70a0 100644 --- a/yarn.lock +++ b/yarn.lock @@ -12078,7 +12078,7 @@ __metadata: "@langchain/core": ">=0.2.21 <0.3.0" "@langchain/scripts": ~0.0.20 "@langchain/standard-tests": 0.0.0 - "@mistralai/mistralai": ^0.4.0 + "@mistralai/mistralai": ^1.0.2 "@swc/core": ^1.3.90 "@swc/jest": ^0.2.29 "@tsconfig/recommended": ^1.0.3 @@ -12733,6 +12733,15 @@ __metadata: languageName: node linkType: hard +"@mistralai/mistralai@npm:^1.0.2": + version: 1.0.2 + resolution: "@mistralai/mistralai@npm:1.0.2" + peerDependencies: + zod: ">= 3" + checksum: 9d2ed8d96d20791571cf9ad2a47c4fb2a8a991543ce95a7daeba8f146cca73cac8e05b9da1e96b16d84a08c3c9bcea90c3e4909ce55dd366ea4931b2e18a28bb + languageName: node + linkType: hard + "@mixedbread-ai/sdk@npm:^2.2.3": version: 2.2.6 resolution: "@mixedbread-ai/sdk@npm:2.2.6" From e87f32a6f0443a12162ab69b7d5249896a7082b8 Mon Sep 17 00:00:00 2001 From: jacoblee93 Date: Tue, 13 Aug 2024 01:46:51 -0700 Subject: [PATCH 002/344] Progress --- libs/langchain-mistralai/src/chat_models.ts | 122 ++++++++++---------- 1 file changed, 58 insertions(+), 64 deletions(-) diff --git a/libs/langchain-mistralai/src/chat_models.ts b/libs/langchain-mistralai/src/chat_models.ts index 72f60c8b79b4..616266bfbbd4 100644 --- a/libs/langchain-mistralai/src/chat_models.ts +++ b/libs/langchain-mistralai/src/chat_models.ts @@ -11,6 +11,17 @@ import { // Message as MistralAIMessage, // TokenUsage as MistralAITokenUsage, } from "@mistralai/mistralai"; +import { + ChatCompletionRequest as MistralAIChatCompletionRequest, + ToolChoice as MistralAIToolChoice, + Messages as MistralAIMessage, +} from "@mistralai/mistralai/models/components/chatcompletionrequest.js"; +import { Tool as MistralAITool } from "@mistralai/mistralai/models/components/tool.js"; +import { ToolCall as MistralAIToolCall } from "@mistralai/mistralai/models/components/toolcall.js"; +import { ChatCompletionStreamRequest as MistralChatCompletionStreamRequest } from "@mistralai/mistralai/models/components/chatcompletionstreamrequest.js"; +import { UsageInfo as MistralAITokenUsage } from "@mistralai/mistralai/models/components/usageinfo.js"; +import { CompletionEvent as MistralAIChatCompletionEvent } from "@mistralai/mistralai/models/components/completionevent.js"; +import { ChatCompletionResponse as MistralChatCompletionResponse } from "@mistralai/mistralai/models/components/chatcompletionresponse.js"; import { MessageType, type BaseMessage, @@ -74,26 +85,7 @@ interface TokenUsage { totalTokens?: number; } -export type MistralAIToolChoice = "auto" | "any" | "none"; - -export type MistralChatCompletionParams = Parameters[0]; -export type MistralAIToolCall = NonNullable[number] - -export type MistralChatCompletionResponse = Awaited>; -export type MistralChatCompletionResponseChunk = Awaited>; -export type MistralChatCompletionChoice = NonNullable[number]; - -export type MistralAITool = NonNullable[number]; -export type MistralAIFunction = MistralAITool["function"]; - -export type MistralAIMessage = NonNullable; - -type MistralAIToolInput = { type: string; function: MistralAIFunction }; - -type ChatMistralAIToolType = - | MistralAIToolInput - | MistralAITool - | BindToolsInput; +type ChatMistralAIToolType = MistralAIToolCall | MistralAITool | BindToolsInput; export interface ChatMistralAICallOptions extends Omit { @@ -257,10 +249,13 @@ function convertMessagesToMistralMessages( } function mistralAIResponseToChatMessage( - choice: MistralChatCompletionResponse["choices"][0], + choice: NonNullable[0], usage?: MistralAITokenUsage ): BaseMessage { const { message } = choice; + if (message === undefined) { + throw new Error("No message found in response"); + } // MistralAI SDK does not include tool_calls in the non // streaming return type, so we need to extract it like this // to satisfy typescript. @@ -288,19 +283,12 @@ function mistralAIResponseToChatMessage( content: message.content ?? "", tool_calls: toolCalls, invalid_tool_calls: invalidToolCalls, - additional_kwargs: { - tool_calls: rawToolCalls.length - ? rawToolCalls.map((toolCall) => ({ - ...toolCall, - type: "function", - })) - : undefined, - }, + additional_kwargs: {}, usage_metadata: usage ? { - input_tokens: usage.prompt_tokens, - output_tokens: usage.completion_tokens, - total_tokens: usage.total_tokens, + input_tokens: usage.promptTokens, + output_tokens: usage.completionTokens, + total_tokens: usage.totalTokens, } : undefined, }); @@ -324,9 +312,9 @@ function _convertDeltaToMessageChunk( content: "", usage_metadata: usage ? { - input_tokens: usage.prompt_tokens, - output_tokens: usage.completion_tokens, - total_tokens: usage.total_tokens, + input_tokens: usage.promptTokens, + output_tokens: usage.completionTokens, + total_tokens: usage.totalTokens, } : undefined, }); @@ -338,7 +326,7 @@ function _convertDeltaToMessageChunk( // need to insert it here. const rawToolCallChunksWithIndex = delta.tool_calls?.length ? delta.tool_calls?.map( - (toolCall, index): OpenAIToolCall => ({ + (toolCall, index): MistralAIToolCall & { index: number } => ({ ...toolCall, index, id: toolCall.id ?? uuidv4().replace(/-/g, ""), @@ -355,13 +343,15 @@ function _convertDeltaToMessageChunk( let additional_kwargs; const toolCallChunks: ToolCallChunk[] = []; if (rawToolCallChunksWithIndex !== undefined) { - additional_kwargs = { - tool_calls: rawToolCallChunksWithIndex, - }; for (const rawToolCallChunk of rawToolCallChunksWithIndex) { + const rawArgs = rawToolCallChunk.function?.arguments; + const args = + rawArgs === undefined || typeof rawArgs === "string" + ? rawArgs + : JSON.stringify(rawArgs); toolCallChunks.push({ name: rawToolCallChunk.function?.name, - args: rawToolCallChunk.function?.arguments, + args, id: rawToolCallChunk.id, index: rawToolCallChunk.index, type: "tool_call_chunk", @@ -380,9 +370,9 @@ function _convertDeltaToMessageChunk( additional_kwargs, usage_metadata: usage ? { - input_tokens: usage.prompt_tokens, - output_tokens: usage.completion_tokens, - total_tokens: usage.total_tokens, + input_tokens: usage.promptTokens, + output_tokens: usage.completionTokens, + total_tokens: usage.totalTokens, } : undefined, }); @@ -832,22 +822,21 @@ export class ChatMistralAI< */ invocationParams( options?: this["ParsedCallOptions"] - ): Omit { + ): Omit { const { response_format, tools, tool_choice } = options ?? {}; const mistralAITools: Array | undefined = tools?.length ? _convertToolToMistralTool(tools) : undefined; - const params: Omit = { + const params: Omit = { model: this.model, tools: mistralAITools, temperature: this.temperature, maxTokens: this.maxTokens, topP: this.topP, randomSeed: this.seed, - safeMode: this.safeMode, safePrompt: this.safePrompt, toolChoice: tool_choice, - responseFormat: response_format as ResponseFormat, + responseFormat: response_format, }; return params; } @@ -868,37 +857,45 @@ export class ChatMistralAI< * @returns {Promise>} The response from the MistralAI API. */ async completionWithRetry( - input: ChatRequest, + input: MistralChatCompletionStreamRequest, streaming: true - ): Promise>; + ): Promise>; async completionWithRetry( - input: ChatRequest, + input: MistralAIChatCompletionRequest, streaming: false ): Promise; async completionWithRetry( - input: ChatRequest, + input: MistralAIChatCompletionRequest | MistralChatCompletionStreamRequest, streaming: boolean ): Promise< - MistralChatCompletionResponse | AsyncGenerator + MistralChatCompletionResponse | AsyncIterable > { - const client = new MistralClient({ apiKey: this.apiKey, serverURL: this.endpoint }); + const client = new MistralClient({ + apiKey: this.apiKey, + serverURL: this.endpoint, + }); return this.caller.call(async () => { try { let res: | MistralChatCompletionResponse - | AsyncGenerator; + | AsyncIterable; if (streaming) { - res = client.chat.stream(input); + res = await client.chat.stream(input); } else { res = await client.chat.complete(input); } return res; // eslint-disable-next-line @typescript-eslint/no-explicit-any } catch (e: any) { - if (e.message?.includes("status: 400")) { + console.log(e, e.status, e.code, e.statusCode, e.message); + if ( + e.message?.includes("status: 400") || + e.message?.toLowerCase().includes("status 400") || + e.message?.includes("validation failed") + ) { e.status = 400; } throw e; @@ -947,11 +944,8 @@ export class ChatMistralAI< // Not streaming, so we can just call the API once. const response = await this.completionWithRetry(input, false); - const { - completion_tokens: completionTokens, - prompt_tokens: promptTokens, - total_tokens: totalTokens, - } = response?.usage ?? {}; + const { completionTokens, promptTokens, totalTokens } = + response?.usage ?? {}; if (completionTokens) { tokenUsage.completionTokens = @@ -979,8 +973,8 @@ export class ChatMistralAI< text, message: mistralAIResponseToChatMessage(part, response?.usage), }; - if (part.finish_reason) { - generation.generationInfo = { finish_reason: part.finish_reason }; + if (part.finishReason) { + generation.generationInfo = { finishReason: part.finishReason }; } generations.push(generation); } @@ -1003,7 +997,7 @@ export class ChatMistralAI< }; const streamIterable = await this.completionWithRetry(input, true); - for await (const data of streamIterable) { + for await (const { data } of streamIterable) { if (options.signal?.aborted) { throw new Error("AbortError"); } From 65e38b56a525b47f4ad7a3bfc23efce20c88e4ce Mon Sep 17 00:00:00 2001 From: CarterMorris Date: Mon, 28 Oct 2024 21:52:32 -0400 Subject: [PATCH 003/344] Finished rough draft migration --- libs/langchain-mistralai/package.json | 4 +- libs/langchain-mistralai/src/chat_models.ts | 14 +-- libs/langchain-mistralai/src/embeddings.ts | 46 ++++++---- libs/langchain-mistralai/src/llms.ts | 98 ++++++++++----------- yarn.lock | 6 +- 5 files changed, 89 insertions(+), 79 deletions(-) diff --git a/libs/langchain-mistralai/package.json b/libs/langchain-mistralai/package.json index 3095517a2fe3..f54aaed376d6 100644 --- a/libs/langchain-mistralai/package.json +++ b/libs/langchain-mistralai/package.json @@ -36,9 +36,9 @@ "license": "MIT", "dependencies": { "@langchain/core": ">=0.2.21 <0.3.0", - "@mistralai/mistralai": "^1.0.2", + "@mistralai/mistralai": "^1.1.0", "uuid": "^10.0.0", - "zod": "^3.22.4", + "zod": "^3.23.8", "zod-to-json-schema": "^3.22.4" }, "devDependencies": { diff --git a/libs/langchain-mistralai/src/chat_models.ts b/libs/langchain-mistralai/src/chat_models.ts index 616266bfbbd4..09c290f61e1b 100644 --- a/libs/langchain-mistralai/src/chat_models.ts +++ b/libs/langchain-mistralai/src/chat_models.ts @@ -13,7 +13,7 @@ import { } from "@mistralai/mistralai"; import { ChatCompletionRequest as MistralAIChatCompletionRequest, - ToolChoice as MistralAIToolChoice, + ChatCompletionRequestToolChoice as MistralAIToolChoice, Messages as MistralAIMessage, } from "@mistralai/mistralai/models/components/chatcompletionrequest.js"; import { Tool as MistralAITool } from "@mistralai/mistralai/models/components/tool.js"; @@ -124,9 +124,9 @@ export interface ChatMistralAIInput */ model?: string; /** - * Override the default endpoint. + * Override the default server URL used by the Mistral SDK. */ - endpoint?: string; + serverURL?: string; /** * What sampling temperature to use, between 0.0 and 2.0. * Higher values like 0.8 will make the output more random, while lower values like 0.2 will make it more focused and deterministic. @@ -755,7 +755,7 @@ export class ChatMistralAI< apiKey: string; - endpoint?: string; + serverURL?: string; temperature = 0.7; @@ -790,7 +790,7 @@ export class ChatMistralAI< } this.apiKey = apiKey; this.streaming = fields?.streaming ?? this.streaming; - this.endpoint = fields?.endpoint; + this.serverURL = fields?.serverURL; this.temperature = fields?.temperature ?? this.temperature; this.topP = fields?.topP ?? this.topP; this.maxTokens = fields?.maxTokens ?? this.maxTokens; @@ -874,7 +874,7 @@ export class ChatMistralAI< > { const client = new MistralClient({ apiKey: this.apiKey, - serverURL: this.endpoint, + serverURL: this.serverURL, }); return this.caller.call(async () => { @@ -1044,7 +1044,7 @@ export class ChatMistralAI< /** @ignore */ _combineLLMOutput() { return []; - } + } withStructuredOutput< // eslint-disable-next-line @typescript-eslint/no-explicit-any diff --git a/libs/langchain-mistralai/src/embeddings.ts b/libs/langchain-mistralai/src/embeddings.ts index f750f53cf5a8..b80186218d3f 100644 --- a/libs/langchain-mistralai/src/embeddings.ts +++ b/libs/langchain-mistralai/src/embeddings.ts @@ -1,7 +1,8 @@ import { getEnvironmentVariable } from "@langchain/core/utils/env"; import { Embeddings, type EmbeddingsParams } from "@langchain/core/embeddings"; import { chunkArray } from "@langchain/core/utils/chunk_array"; -import { EmbeddingResponse } from "@mistralai/mistralai"; +import { EmbeddingRequest as MistralAIEmbeddingsRequest} from "@mistralai/mistralai/src/models/components/embeddingrequest.js"; +import { EmbeddingResponse as MistralAIEmbeddingsResult} from "@mistralai/mistralai/src/models/components/embeddingresponse.js"; /** * Interface for MistralAIEmbeddings parameters. Extends EmbeddingsParams and @@ -30,9 +31,9 @@ export interface MistralAIEmbeddingsParams extends EmbeddingsParams { */ encodingFormat?: string; /** - * Override the default endpoint. + * Override the default server URL used by the Mistral SDK. */ - endpoint?: string; + serverURL?: string; /** * The maximum number of documents to embed in a single request. * @default {512} @@ -65,7 +66,7 @@ export class MistralAIEmbeddings apiKey: string; - endpoint?: string; + serverURL?: string; constructor(fields?: Partial) { super(fields ?? {}); @@ -74,7 +75,7 @@ export class MistralAIEmbeddings throw new Error("API key missing for MistralAI, but it is required."); } this.apiKey = apiKey; - this.endpoint = fields?.endpoint; + this.serverURL = fields?.serverURL; this.modelName = fields?.model ?? fields?.modelName ?? this.model; this.model = this.modelName; this.encodingFormat = fields?.encodingFormat ?? this.encodingFormat; @@ -105,7 +106,7 @@ export class MistralAIEmbeddings const batch = batches[i]; const { data: batchResponse } = batchResponses[i]; for (let j = 0; j < batch.length; j += 1) { - embeddings.push(batchResponse[j].embedding); + embeddings.push(batchResponse[j].embedding ?? []); } } return embeddings; @@ -121,33 +122,42 @@ export class MistralAIEmbeddings const { data } = await this.embeddingWithRetry( this.stripNewLines ? text.replace(/\n/g, " ") : text ); - return data[0].embedding; + return data[0].embedding ?? []; } /** * Private method to make a request to the MistralAI API to generate * embeddings. Handles the retry logic and returns the response from the * API. - * @param {string | Array} input Text to send to the MistralAI API. + * @param {string | Array} inputs Text to send to the MistralAI API. * @returns {Promise} Promise that resolves to the response from the API. */ private async embeddingWithRetry( - input: string | Array - ): Promise { - const { MistralClient } = await this.imports(); - const client = new MistralClient(this.apiKey, this.endpoint); + inputs: string | Array + ): Promise { + const { Mistral } = await this.imports(); + const client = new Mistral({ + apiKey: this.apiKey, + serverURL: this.serverURL, + // Could add hooks here + }); + let embeddingsRequest: MistralAIEmbeddingsRequest = { + model: this.model, + inputs, + encodingFormat: this.encodingFormat + } return this.caller.call(async () => { - const res = await client.embeddings({ - model: this.model, - input, - }); + const res = await client.embeddings.create( + embeddingsRequest, + // Could add options here + ); return res; }); } /** @ignore */ private async imports() { - const { default: MistralClient } = await import("@mistralai/mistralai"); - return { MistralClient }; + const { Mistral } = await import("@mistralai/mistralai"); + return { Mistral }; } } diff --git a/libs/langchain-mistralai/src/llms.ts b/libs/langchain-mistralai/src/llms.ts index 944c18ea67b6..bb35df375974 100644 --- a/libs/langchain-mistralai/src/llms.ts +++ b/libs/langchain-mistralai/src/llms.ts @@ -2,12 +2,11 @@ import { CallbackManagerForLLMRun } from "@langchain/core/callbacks/manager"; import { BaseLLMParams, LLM } from "@langchain/core/language_models/llms"; import { type BaseLanguageModelCallOptions } from "@langchain/core/language_models/base"; import { GenerationChunk, LLMResult } from "@langchain/core/outputs"; -import { - ChatCompletionResponse, - ChatCompletionResponseChoice, - ChatCompletionResponseChunk, - type CompletionRequest, -} from "@mistralai/mistralai"; +import { FIMCompletionRequest as MistralFIMCompletionRequest } from "@mistralai/mistralai/models/components/fimcompletionrequest.js"; +import { FIMCompletionStreamRequest as MistralFIMCompletionStreamRequest} from "@mistralai/mistralai/models/components/fimcompletionstreamrequest.js"; +import { FIMCompletionResponse as MistralFIMCompletionResponse } from "@mistralai/mistralai/models/components/fimcompletionresponse.js"; +import { ChatCompletionChoice as MistralChatCompletionChoice} from "@mistralai/mistralai/models/components/chatcompletionchoice.js"; +import { CompletionEvent as MistralCompletionEvent } from "@mistralai/mistralai/models/components/completionevent.js"; import { getEnvironmentVariable } from "@langchain/core/utils/env"; import { chunkArray } from "@langchain/core/utils/chunk_array"; import { AsyncCaller } from "@langchain/core/utils/async_caller"; @@ -34,9 +33,9 @@ export interface MistralAIInput extends BaseLLMParams { */ apiKey?: string; /** - * Override the default endpoint. + * Override the default server URL used by the Mistral SDK. */ - endpoint?: string; + serverURL?: string; /** * What sampling temperature to use, between 0.0 and 2.0. * Higher values like 0.8 will make the output more random, while lower values like 0.2 will make it more focused and deterministic. @@ -98,7 +97,7 @@ export class MistralAI apiKey: string; - endpoint?: string; + serverURL?: string; maxRetries?: number; @@ -114,7 +113,7 @@ export class MistralAI this.randomSeed = fields?.randomSeed ?? this.randomSeed; this.batchSize = fields?.batchSize ?? this.batchSize; this.streaming = fields?.streaming ?? this.streaming; - this.endpoint = fields?.endpoint; + this.serverURL = fields?.serverURL; this.maxRetries = fields?.maxRetries; this.maxConcurrency = fields?.maxConcurrency; @@ -140,7 +139,7 @@ Either provide one via the "apiKey" field in the constructor, or set the "MISTRA invocationParams( options: this["ParsedCallOptions"] - ): Omit { + ): Omit { return { model: this.model, suffix: options.suffix, @@ -167,7 +166,7 @@ Either provide one via the "apiKey" field in the constructor, or set the "MISTRA prompt, }; const result = await this.completionWithRetry(params, options, false); - return result.choices[0].message.content ?? ""; + return result?.choices?.[0].message.content ?? ""; } async _generate( @@ -176,7 +175,7 @@ Either provide one via the "apiKey" field in the constructor, or set the "MISTRA runManager?: CallbackManagerForLLMRun ): Promise { const subPrompts = chunkArray(prompts, this.batchSize); - const choices: ChatCompletionResponseChoice[][] = []; + const choices: MistralChatCompletionChoice[][] = []; const params = this.invocationParams(options); @@ -184,16 +183,16 @@ Either provide one via the "apiKey" field in the constructor, or set the "MISTRA const data = await (async () => { if (this.streaming) { const responseData: Array< - { choices: ChatCompletionResponseChoice[] } & Partial< - Omit + { choices: MistralChatCompletionChoice[] } & Partial< + Omit > > = []; for (let x = 0; x < subPrompts[i].length; x += 1) { - const choices: ChatCompletionResponseChoice[] = []; + const choices: MistralChatCompletionChoice[] = []; let response: - | Omit + | Omit | undefined; - const stream = await this.completionWithRetry( + const stream = await this.completionWithRetry( { ...params, prompt: subPrompts[i][x], @@ -202,32 +201,33 @@ Either provide one via the "apiKey" field in the constructor, or set the "MISTRA true ); for await (const message of stream) { + const data = message.data // on the first message set the response properties if (!response) { response = { - id: message.id, + id: data.id, object: "chat.completion", - created: message.created, - model: message.model, + created: data.created, + model: data.model, }; } // on all messages, update choice - for (const part of message.choices) { + for (const part of data.choices) { if (!choices[part.index]) { choices[part.index] = { index: part.index, message: { - role: part.delta.role ?? "assistant", + role: "assistant", content: part.delta.content ?? "", - tool_calls: null, + toolCalls: null, }, - finish_reason: part.finish_reason, + finishReason: part.finishReason ?? "model_length", }; } else { const choice = choices[part.index]; choice.message.content += part.delta.content ?? ""; - choice.finish_reason = part.finish_reason; + choice.finishReason = part.finishReason ?? "model_length"; } void runManager?.handleLLMNewToken(part.delta.content ?? "", { prompt: part.index, @@ -245,7 +245,7 @@ Either provide one via the "apiKey" field in the constructor, or set the "MISTRA } return responseData; } else { - const responseData: Array = []; + const responseData: Array = []; for (let x = 0; x < subPrompts[i].length; x += 1) { const res = await this.completionWithRetry( { @@ -261,14 +261,14 @@ Either provide one via the "apiKey" field in the constructor, or set the "MISTRA } })(); - choices.push(...data.map((d) => d.choices)); + choices.push(...data.map((d) => d.choices ?? [])); } const generations = choices.map((promptChoices) => promptChoices.map((choice) => ({ text: choice.message.content ?? "", generationInfo: { - finishReason: choice.finish_reason, + finishReason: choice.finishReason, }, })) ); @@ -278,45 +278,44 @@ Either provide one via the "apiKey" field in the constructor, or set the "MISTRA } async completionWithRetry( - request: CompletionRequest, + request: MistralFIMCompletionRequest, options: this["ParsedCallOptions"], stream: false - ): Promise; + ): Promise; async completionWithRetry( - request: CompletionRequest, + request: MistralFIMCompletionStreamRequest, options: this["ParsedCallOptions"], stream: true - ): Promise>; + ): Promise>; async completionWithRetry( - request: CompletionRequest, + request: MistralFIMCompletionRequest | MistralFIMCompletionStreamRequest, options: this["ParsedCallOptions"], stream: boolean ): Promise< - | ChatCompletionResponse - | AsyncGenerator + MistralFIMCompletionResponse | AsyncIterable > { - const { MistralClient } = await this.imports(); + const { Mistral } = await this.imports(); const caller = new AsyncCaller({ maxConcurrency: options.maxConcurrency || this.maxConcurrency, maxRetries: this.maxRetries, }); - const client = new MistralClient( - this.apiKey, - this.endpoint, - this.maxRetries, - options.timeout - ); + const client = new Mistral({ + apiKey: this.apiKey, + serverURL: this.serverURL, + timeoutMs: options.timeout, + // this.maxRetries, + }); return caller.callWithOptions( { signal: options.signal, }, async () => { if (stream) { - return client.completionStream(request); + return client.fim.stream(request); } else { - return client.completion(request); + return client.fim.complete(request); } } ); @@ -332,7 +331,8 @@ Either provide one via the "apiKey" field in the constructor, or set the "MISTRA prompt, }; const stream = await this.completionWithRetry(params, options, true); - for await (const data of stream) { + for await (const message of stream) { + const data = message.data const choice = data?.choices[0]; if (!choice) { continue; @@ -340,7 +340,7 @@ Either provide one via the "apiKey" field in the constructor, or set the "MISTRA const chunk = new GenerationChunk({ text: choice.delta.content ?? "", generationInfo: { - finishReason: choice.finish_reason, + finishReason: choice.finishReason, tokenUsage: data.usage, }, }); @@ -355,7 +355,7 @@ Either provide one via the "apiKey" field in the constructor, or set the "MISTRA /** @ignore */ private async imports() { - const { default: MistralClient } = await import("@mistralai/mistralai"); - return { MistralClient }; + const { Mistral } = await import("@mistralai/mistralai"); + return { Mistral }; } } diff --git a/yarn.lock b/yarn.lock index 2114b12d70a0..18b9eab032c7 100644 --- a/yarn.lock +++ b/yarn.lock @@ -12062,7 +12062,7 @@ __metadata: resolution: "@langchain/mistralai@npm:0.0.26" dependencies: "@langchain/core": ">=0.2.16 <0.3.0" - "@mistralai/mistralai": ^0.4.0 + "@mistralai/mistralai": ^1.1.0 uuid: ^10.0.0 zod: ^3.22.4 zod-to-json-schema: ^3.22.4 @@ -12078,7 +12078,7 @@ __metadata: "@langchain/core": ">=0.2.21 <0.3.0" "@langchain/scripts": ~0.0.20 "@langchain/standard-tests": 0.0.0 - "@mistralai/mistralai": ^1.0.2 + "@mistralai/mistralai": ^1.1.0 "@swc/core": ^1.3.90 "@swc/jest": ^0.2.29 "@tsconfig/recommended": ^1.0.3 @@ -12100,7 +12100,7 @@ __metadata: ts-jest: ^29.1.0 typescript: <5.2.0 uuid: ^10.0.0 - zod: ^3.22.4 + zod: ^3.23.8 zod-to-json-schema: ^3.22.4 languageName: unknown linkType: soft From f5cbdff0e4e8e02321dae924f7a2f38aa4cb73a7 Mon Sep 17 00:00:00 2001 From: CarterMorris Date: Tue, 29 Oct 2024 17:49:46 -0400 Subject: [PATCH 004/344] Added support for ContentChunk message type input to chat model --- libs/langchain-mistralai/src/chat_models.ts | 77 ++++++++++++++++----- libs/langchain-mistralai/src/llms.ts | 34 ++++++--- 2 files changed, 85 insertions(+), 26 deletions(-) diff --git a/libs/langchain-mistralai/src/chat_models.ts b/libs/langchain-mistralai/src/chat_models.ts index 09c290f61e1b..0ac17166326c 100644 --- a/libs/langchain-mistralai/src/chat_models.ts +++ b/libs/langchain-mistralai/src/chat_models.ts @@ -1,21 +1,11 @@ import { v4 as uuidv4 } from "uuid"; -import { - Mistral as MistralClient, - // MistralChatCompletionResponse, - // Function as MistralAIFunction, - // ToolCalls as MistralAIToolCalls, - // ResponseFormat, - // MistralChatCompletionResponseChunk, - // ChatRequest, - // Tool as MistralAITool, - // Message as MistralAIMessage, - // TokenUsage as MistralAITokenUsage, -} from "@mistralai/mistralai"; +import { Mistral as MistralClient } from "@mistralai/mistralai"; import { ChatCompletionRequest as MistralAIChatCompletionRequest, ChatCompletionRequestToolChoice as MistralAIToolChoice, Messages as MistralAIMessage, } from "@mistralai/mistralai/models/components/chatcompletionrequest.js"; +import { ContentChunk } from "@mistralai/mistralai/models/components/contentchunk.js"; import { Tool as MistralAITool } from "@mistralai/mistralai/models/components/tool.js"; import { ToolCall as MistralAIToolCall } from "@mistralai/mistralai/models/components/toolcall.js"; import { ChatCompletionStreamRequest as MistralChatCompletionStreamRequest } from "@mistralai/mistralai/models/components/chatcompletionstreamrequest.js"; @@ -192,12 +182,59 @@ function convertMessagesToMistralMessages( } }; - const getContent = (content: MessageContent): string => { + const getContent = (content: MessageContent, role: MessageType): string | ContentChunk[] => { + const mistralRole = getRole(role) + if (typeof content === "string") { return content; } + else if (Array.isArray(content)) { + if (mistralRole === "user") { + return content.map((messageContentComplex) => { + if (messageContentComplex?.type === "image_url") { + return { + type: messageContentComplex.type, + imageUrl: messageContentComplex?.image_url + } as ContentChunk; + } + else if (messageContentComplex?.type === "text"){ + return { + type: messageContentComplex.type, + text: messageContentComplex?.text + } as ContentChunk; + } + throw new Error( + `ChatMistralAI only supports messages of type MessageContentText + and MessageContentImageUrl for role "human". Received: ${JSON.stringify( + content, + null, + 2 + )}` + ); + }); + } + else if (mistralRole === "system") { + return content.map((messageContentComplex) => { + if (messageContentComplex?.type === "text"){ + return { + type: messageContentComplex.type, + text: messageContentComplex?.text + } as ContentChunk; + } + throw new Error( + `ChatMistralAI only supports messages of type MessageContentText + for role "system". Received: ${JSON.stringify( + content, + null, + 2 + )}` + ); + }); + } + } throw new Error( - `ChatMistralAI does not support non text message content. Received: ${JSON.stringify( + `ChatMistralAI does not support non text message content for role "ai", "tool", + or "function". Received: ${JSON.stringify( content, null, 2 @@ -228,22 +265,28 @@ function convertMessagesToMistralMessages( return messages.map((message) => { const toolCalls = getTools(message); - const content = toolCalls === undefined ? getContent(message.content) : ""; + const content = getContent(message.content, message._getType()); if ("tool_call_id" in message && typeof message.tool_call_id === "string") { return { role: getRole(message._getType()), content, name: message.name, - tool_call_id: _convertToolCallIdToMistralCompatible( + toolCallId: _convertToolCallIdToMistralCompatible( message.tool_call_id ), }; } + else if (isAIMessage(message)) { + return { + role: getRole(message._getType()), + content, + toolCalls: toolCalls, + }; + } return { role: getRole(message._getType()), content, - tool_calls: toolCalls, }; }) as MistralAIMessage[]; } diff --git a/libs/langchain-mistralai/src/llms.ts b/libs/langchain-mistralai/src/llms.ts index bb35df375974..e39501935a51 100644 --- a/libs/langchain-mistralai/src/llms.ts +++ b/libs/langchain-mistralai/src/llms.ts @@ -6,7 +6,7 @@ import { FIMCompletionRequest as MistralFIMCompletionRequest } from "@mistralai/ import { FIMCompletionStreamRequest as MistralFIMCompletionStreamRequest} from "@mistralai/mistralai/models/components/fimcompletionstreamrequest.js"; import { FIMCompletionResponse as MistralFIMCompletionResponse } from "@mistralai/mistralai/models/components/fimcompletionresponse.js"; import { ChatCompletionChoice as MistralChatCompletionChoice} from "@mistralai/mistralai/models/components/chatcompletionchoice.js"; -import { CompletionEvent as MistralCompletionEvent } from "@mistralai/mistralai/models/components/completionevent.js"; +import { CompletionEvent as MistralChatCompletionEvent } from "@mistralai/mistralai/models/components/completionevent.js"; import { getEnvironmentVariable } from "@langchain/core/utils/env"; import { chunkArray } from "@langchain/core/utils/chunk_array"; import { AsyncCaller } from "@langchain/core/utils/async_caller"; @@ -200,8 +200,7 @@ Either provide one via the "apiKey" field in the constructor, or set the "MISTRA options, true ); - for await (const message of stream) { - const data = message.data + for await (const { data } of stream) { // on the first message set the response properties if (!response) { response = { @@ -287,14 +286,14 @@ Either provide one via the "apiKey" field in the constructor, or set the "MISTRA request: MistralFIMCompletionStreamRequest, options: this["ParsedCallOptions"], stream: true - ): Promise>; + ): Promise>; async completionWithRetry( request: MistralFIMCompletionRequest | MistralFIMCompletionStreamRequest, options: this["ParsedCallOptions"], stream: boolean ): Promise< - MistralFIMCompletionResponse | AsyncIterable + MistralFIMCompletionResponse | AsyncIterable > { const { Mistral } = await this.imports(); const caller = new AsyncCaller({ @@ -312,10 +311,27 @@ Either provide one via the "apiKey" field in the constructor, or set the "MISTRA signal: options.signal, }, async () => { - if (stream) { - return client.fim.stream(request); - } else { - return client.fim.complete(request); + try { + let res: + | MistralFIMCompletionResponse + | AsyncIterable; + if (stream) { + res = await client.fim.stream(request); + } else { + res = await client.fim.complete(request); + } + return res; + // eslint-disable-next-line @typescript-eslint/no-explicit-any + } catch (e: any) { + console.log(e, e.status, e.code, e.statusCode, e.message); + if ( + e.message?.includes("status: 400") || + e.message?.toLowerCase().includes("status 400") || + e.message?.includes("validation failed") + ) { + e.status = 400; + } + throw e; } } ); From c4e643360718eb11b2779558f403987a694448c7 Mon Sep 17 00:00:00 2001 From: CarterMorris Date: Wed, 30 Oct 2024 16:15:58 -0400 Subject: [PATCH 005/344] Added delta msg null type options and fixed shouldStream nullish coalescing --- libs/langchain-mistralai/src/chat_models.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/libs/langchain-mistralai/src/chat_models.ts b/libs/langchain-mistralai/src/chat_models.ts index 0ac17166326c..ad2dae3bffa4 100644 --- a/libs/langchain-mistralai/src/chat_models.ts +++ b/libs/langchain-mistralai/src/chat_models.ts @@ -344,8 +344,8 @@ function mistralAIResponseToChatMessage( function _convertDeltaToMessageChunk( delta: { role?: string | undefined; - content?: string | undefined; - tool_calls?: MistralAIToolCall[] | undefined; + content?: string | null | undefined; + tool_calls?: MistralAIToolCall[] | null | undefined; }, usage?: MistralAITokenUsage | null ) { @@ -962,7 +962,7 @@ export class ChatMistralAI< // Enable streaming for signal controller or timeout due // to SDK limitations on canceling requests. - const shouldStream = !!options.signal ?? !!options.timeout; + const shouldStream = options.signal ?? !!options.timeout; // Handle streaming if (this.streaming || shouldStream) { From 6894fbbb19dbbcc371ce7a9e14be213676bd96d6 Mon Sep 17 00:00:00 2001 From: Ashtian Date: Wed, 30 Oct 2024 22:01:26 -0400 Subject: [PATCH 006/344] simplified logic for getContent() --- libs/langchain-mistralai/src/chat_models.ts | 78 +++++++++------------ 1 file changed, 33 insertions(+), 45 deletions(-) diff --git a/libs/langchain-mistralai/src/chat_models.ts b/libs/langchain-mistralai/src/chat_models.ts index ad2dae3bffa4..12063594d009 100644 --- a/libs/langchain-mistralai/src/chat_models.ts +++ b/libs/langchain-mistralai/src/chat_models.ts @@ -5,7 +5,7 @@ import { ChatCompletionRequestToolChoice as MistralAIToolChoice, Messages as MistralAIMessage, } from "@mistralai/mistralai/models/components/chatcompletionrequest.js"; -import { ContentChunk } from "@mistralai/mistralai/models/components/contentchunk.js"; +import { ContentChunk, ContentChunk$ } from "@mistralai/mistralai/models/components/contentchunk.js"; import { Tool as MistralAITool } from "@mistralai/mistralai/models/components/tool.js"; import { ToolCall as MistralAIToolCall } from "@mistralai/mistralai/models/components/toolcall.js"; import { ChatCompletionStreamRequest as MistralChatCompletionStreamRequest } from "@mistralai/mistralai/models/components/chatcompletionstreamrequest.js"; @@ -183,55 +183,43 @@ function convertMessagesToMistralMessages( }; const getContent = (content: MessageContent, role: MessageType): string | ContentChunk[] => { - const mistralRole = getRole(role) + const mistralRole = getRole(role); + + const _generateContentChunk = (complex: any, role: string): ContentChunk => { + if (complex.type === "image_url" && role === "user") { + return { + type: complex.type, + imageUrl: complex?.image_url + } as ContentChunk; + } + + if (complex.type === "text" && (role === "user" || role === "system")){ + return { + type: complex.type, + text: complex?.text + } as ContentChunk; + } + + throw new Error( + `ChatMistralAI only supports messages of type MessageContentText for role "human" + and "system" and MessageContentImageUrl for role "human". Received: ${JSON.stringify( + content, + null, + 2 + )}` + ); + } if (typeof content === "string") { return content; } - else if (Array.isArray(content)) { - if (mistralRole === "user") { - return content.map((messageContentComplex) => { - if (messageContentComplex?.type === "image_url") { - return { - type: messageContentComplex.type, - imageUrl: messageContentComplex?.image_url - } as ContentChunk; - } - else if (messageContentComplex?.type === "text"){ - return { - type: messageContentComplex.type, - text: messageContentComplex?.text - } as ContentChunk; - } - throw new Error( - `ChatMistralAI only supports messages of type MessageContentText - and MessageContentImageUrl for role "human". Received: ${JSON.stringify( - content, - null, - 2 - )}` - ); - }); - } - else if (mistralRole === "system") { - return content.map((messageContentComplex) => { - if (messageContentComplex?.type === "text"){ - return { - type: messageContentComplex.type, - text: messageContentComplex?.text - } as ContentChunk; - } - throw new Error( - `ChatMistralAI only supports messages of type MessageContentText - for role "system". Received: ${JSON.stringify( - content, - null, - 2 - )}` - ); - }); - } + + if (Array.isArray(content)) { + return content.map((messageContentComplex) => { + return _generateContentChunk(messageContentComplex, mistralRole); + }); } + throw new Error( `ChatMistralAI does not support non text message content for role "ai", "tool", or "function". Received: ${JSON.stringify( From 4150202a3f9502ee4778bb0aef7a32cb373d0c85 Mon Sep 17 00:00:00 2001 From: Ashtian Date: Sun, 3 Nov 2024 08:55:21 -0500 Subject: [PATCH 007/344] Added support for new HTTPClient feature in v1.1.0 --- libs/langchain-mistralai/src/chat_models.ts | 16 ++++++++++++++++ libs/langchain-mistralai/src/embeddings.ts | 13 ++++++++++++- libs/langchain-mistralai/src/llms.ts | 12 +++++++++++- 3 files changed, 39 insertions(+), 2 deletions(-) diff --git a/libs/langchain-mistralai/src/chat_models.ts b/libs/langchain-mistralai/src/chat_models.ts index 12063594d009..1c57c1619c79 100644 --- a/libs/langchain-mistralai/src/chat_models.ts +++ b/libs/langchain-mistralai/src/chat_models.ts @@ -12,6 +12,7 @@ import { ChatCompletionStreamRequest as MistralChatCompletionStreamRequest } fro import { UsageInfo as MistralAITokenUsage } from "@mistralai/mistralai/models/components/usageinfo.js"; import { CompletionEvent as MistralAIChatCompletionEvent } from "@mistralai/mistralai/models/components/completionevent.js"; import { ChatCompletionResponse as MistralChatCompletionResponse } from "@mistralai/mistralai/models/components/chatcompletionresponse.js"; +import { HTTPClient } from "@mistralai/mistralai/lib/http.js"; import { MessageType, type BaseMessage, @@ -160,6 +161,11 @@ export interface ChatMistralAIInput * The seed to use for random sampling. If set, different calls will generate deterministic results. */ seed?: number; + /** + * + */ + httpClient?: HTTPClient | undefined; + } function convertMessagesToMistralMessages( @@ -811,6 +817,12 @@ export class ChatMistralAI< streamUsage = true; + /** + * Optional custom HTTP client to manage API requests + * Allows users to add custom fetch implementations, hooks, as well as error and response processing. + */ + httpClient?: HTTPClient; + constructor(fields?: ChatMistralAIInput) { super(fields ?? {}); const apiKey = fields?.apiKey ?? getEnvironmentVariable("MISTRAL_API_KEY"); @@ -828,9 +840,11 @@ export class ChatMistralAI< this.safePrompt = fields?.safePrompt ?? this.safePrompt; this.randomSeed = fields?.seed ?? fields?.randomSeed ?? this.seed; this.seed = this.randomSeed; + this.httpClient = fields?.httpClient; this.modelName = fields?.model ?? fields?.modelName ?? this.model; this.model = this.modelName; this.streamUsage = fields?.streamUsage ?? this.streamUsage; + this.httpClient = fields?.httpClient ?? undefined; } getLsParams(options: this["ParsedCallOptions"]): LangSmithParams { @@ -906,6 +920,8 @@ export class ChatMistralAI< const client = new MistralClient({ apiKey: this.apiKey, serverURL: this.serverURL, + // If httpClient exists, pass it into constructor + ...( this.httpClient ? {httpCLient: this.httpClient} : {}) }); return this.caller.call(async () => { diff --git a/libs/langchain-mistralai/src/embeddings.ts b/libs/langchain-mistralai/src/embeddings.ts index b80186218d3f..5faefc35ac69 100644 --- a/libs/langchain-mistralai/src/embeddings.ts +++ b/libs/langchain-mistralai/src/embeddings.ts @@ -3,6 +3,7 @@ import { Embeddings, type EmbeddingsParams } from "@langchain/core/embeddings"; import { chunkArray } from "@langchain/core/utils/chunk_array"; import { EmbeddingRequest as MistralAIEmbeddingsRequest} from "@mistralai/mistralai/src/models/components/embeddingrequest.js"; import { EmbeddingResponse as MistralAIEmbeddingsResult} from "@mistralai/mistralai/src/models/components/embeddingresponse.js"; +import { HTTPClient } from "@mistralai/mistralai/lib/http.js"; /** * Interface for MistralAIEmbeddings parameters. Extends EmbeddingsParams and @@ -45,6 +46,12 @@ export interface MistralAIEmbeddingsParams extends EmbeddingsParams { * @default {true} */ stripNewLines?: boolean; + /** + * Optional custom HTTP client to manage API requests + * Allows users to add custom fetch implementations, hooks, as well as error and response processing. + */ + httpCLient?: HTTPClient; + } /** @@ -68,6 +75,8 @@ export class MistralAIEmbeddings serverURL?: string; + httpClient?: HTTPClient; + constructor(fields?: Partial) { super(fields ?? {}); const apiKey = fields?.apiKey ?? getEnvironmentVariable("MISTRAL_API_KEY"); @@ -81,6 +90,7 @@ export class MistralAIEmbeddings this.encodingFormat = fields?.encodingFormat ?? this.encodingFormat; this.batchSize = fields?.batchSize ?? this.batchSize; this.stripNewLines = fields?.stripNewLines ?? this.stripNewLines; + this.httpClient = fields?.httpCLient ?? undefined; } /** @@ -139,7 +149,8 @@ export class MistralAIEmbeddings const client = new Mistral({ apiKey: this.apiKey, serverURL: this.serverURL, - // Could add hooks here + // If httpClient exists, pass it into constructor + ...( this.httpClient ? {httpCLient: this.httpClient} : {}) }); let embeddingsRequest: MistralAIEmbeddingsRequest = { model: this.model, diff --git a/libs/langchain-mistralai/src/llms.ts b/libs/langchain-mistralai/src/llms.ts index e39501935a51..3e4e1529864d 100644 --- a/libs/langchain-mistralai/src/llms.ts +++ b/libs/langchain-mistralai/src/llms.ts @@ -7,6 +7,7 @@ import { FIMCompletionStreamRequest as MistralFIMCompletionStreamRequest} from " import { FIMCompletionResponse as MistralFIMCompletionResponse } from "@mistralai/mistralai/models/components/fimcompletionresponse.js"; import { ChatCompletionChoice as MistralChatCompletionChoice} from "@mistralai/mistralai/models/components/chatcompletionchoice.js"; import { CompletionEvent as MistralChatCompletionEvent } from "@mistralai/mistralai/models/components/completionevent.js"; +import { HTTPClient } from "@mistralai/mistralai/lib/http.js"; import { getEnvironmentVariable } from "@langchain/core/utils/env"; import { chunkArray } from "@langchain/core/utils/chunk_array"; import { AsyncCaller } from "@langchain/core/utils/async_caller"; @@ -68,6 +69,11 @@ export interface MistralAIInput extends BaseLLMParams { * Batch size to use when passing multiple documents to generate */ batchSize?: number; + /** + * Optional custom HTTP client to manage API requests + * Allows users to add custom fetch implementations, hooks, as well as error and response processing. + */ + httpClient?: HTTPClient; } /** @@ -103,6 +109,8 @@ export class MistralAI maxConcurrency?: number; + httpClient?: HTTPClient; + constructor(fields?: MistralAIInput) { super(fields ?? {}); @@ -116,6 +124,7 @@ export class MistralAI this.serverURL = fields?.serverURL; this.maxRetries = fields?.maxRetries; this.maxConcurrency = fields?.maxConcurrency; + this.httpClient = fields?.httpClient ?? undefined; const apiKey = fields?.apiKey ?? getEnvironmentVariable("MISTRAL_API_KEY"); if (!apiKey) { @@ -304,7 +313,8 @@ Either provide one via the "apiKey" field in the constructor, or set the "MISTRA apiKey: this.apiKey, serverURL: this.serverURL, timeoutMs: options.timeout, - // this.maxRetries, + // If httpClient exists, pass it into constructor + ...( this.httpClient ? {httpCLient: this.httpClient} : {}) }); return caller.callWithOptions( { From 284c2fc043f12870b32174672e87e0e9e95ea697 Mon Sep 17 00:00:00 2001 From: CarterMorris Date: Tue, 5 Nov 2024 16:09:54 -0500 Subject: [PATCH 008/344] Unified mistral import naming scheme --- libs/langchain-mistralai/src/chat_models.ts | 35 ++++++++------- libs/langchain-mistralai/src/embeddings.ts | 16 +++---- libs/langchain-mistralai/src/llms.ts | 48 ++++++++++----------- 3 files changed, 49 insertions(+), 50 deletions(-) diff --git a/libs/langchain-mistralai/src/chat_models.ts b/libs/langchain-mistralai/src/chat_models.ts index 1c57c1619c79..0b7dfa4d7724 100644 --- a/libs/langchain-mistralai/src/chat_models.ts +++ b/libs/langchain-mistralai/src/chat_models.ts @@ -5,14 +5,14 @@ import { ChatCompletionRequestToolChoice as MistralAIToolChoice, Messages as MistralAIMessage, } from "@mistralai/mistralai/models/components/chatcompletionrequest.js"; -import { ContentChunk, ContentChunk$ } from "@mistralai/mistralai/models/components/contentchunk.js"; +import { ContentChunk as MistralAIContentChunk} from "@mistralai/mistralai/models/components/contentchunk.js"; import { Tool as MistralAITool } from "@mistralai/mistralai/models/components/tool.js"; import { ToolCall as MistralAIToolCall } from "@mistralai/mistralai/models/components/toolcall.js"; -import { ChatCompletionStreamRequest as MistralChatCompletionStreamRequest } from "@mistralai/mistralai/models/components/chatcompletionstreamrequest.js"; +import { ChatCompletionStreamRequest as MistralAIChatCompletionStreamRequest } from "@mistralai/mistralai/models/components/chatcompletionstreamrequest.js"; import { UsageInfo as MistralAITokenUsage } from "@mistralai/mistralai/models/components/usageinfo.js"; import { CompletionEvent as MistralAIChatCompletionEvent } from "@mistralai/mistralai/models/components/completionevent.js"; -import { ChatCompletionResponse as MistralChatCompletionResponse } from "@mistralai/mistralai/models/components/chatcompletionresponse.js"; -import { HTTPClient } from "@mistralai/mistralai/lib/http.js"; +import { ChatCompletionResponse as MistralAIChatCompletionResponse } from "@mistralai/mistralai/models/components/chatcompletionresponse.js"; +import { HTTPClient as MistralAIHTTPClient} from "@mistralai/mistralai/lib/http.js"; import { MessageType, type BaseMessage, @@ -164,7 +164,7 @@ export interface ChatMistralAIInput /** * */ - httpClient?: HTTPClient | undefined; + httpClient?: MistralAIHTTPClient | undefined; } @@ -188,22 +188,22 @@ function convertMessagesToMistralMessages( } }; - const getContent = (content: MessageContent, role: MessageType): string | ContentChunk[] => { + const getContent = (content: MessageContent, role: MessageType): string | MistralAIContentChunk[] => { const mistralRole = getRole(role); - const _generateContentChunk = (complex: any, role: string): ContentChunk => { + const _generateContentChunk = (complex: any, role: string): MistralAIContentChunk => { if (complex.type === "image_url" && role === "user") { return { type: complex.type, imageUrl: complex?.image_url - } as ContentChunk; + } as MistralAIContentChunk; } if (complex.type === "text" && (role === "user" || role === "system")){ return { type: complex.type, text: complex?.text - } as ContentChunk; + } as MistralAIContentChunk; } throw new Error( @@ -286,7 +286,7 @@ function convertMessagesToMistralMessages( } function mistralAIResponseToChatMessage( - choice: NonNullable[0], + choice: NonNullable[0], usage?: MistralAITokenUsage ): BaseMessage { const { message } = choice; @@ -821,7 +821,7 @@ export class ChatMistralAI< * Optional custom HTTP client to manage API requests * Allows users to add custom fetch implementations, hooks, as well as error and response processing. */ - httpClient?: HTTPClient; + httpClient?: MistralAIHTTPClient; constructor(fields?: ChatMistralAIInput) { super(fields ?? {}); @@ -840,7 +840,6 @@ export class ChatMistralAI< this.safePrompt = fields?.safePrompt ?? this.safePrompt; this.randomSeed = fields?.seed ?? fields?.randomSeed ?? this.seed; this.seed = this.randomSeed; - this.httpClient = fields?.httpClient; this.modelName = fields?.model ?? fields?.modelName ?? this.model; this.model = this.modelName; this.streamUsage = fields?.streamUsage ?? this.streamUsage; @@ -902,32 +901,32 @@ export class ChatMistralAI< * @returns {Promise>} The response from the MistralAI API. */ async completionWithRetry( - input: MistralChatCompletionStreamRequest, + input: MistralAIChatCompletionStreamRequest, streaming: true ): Promise>; async completionWithRetry( input: MistralAIChatCompletionRequest, streaming: false - ): Promise; + ): Promise; async completionWithRetry( - input: MistralAIChatCompletionRequest | MistralChatCompletionStreamRequest, + input: MistralAIChatCompletionRequest | MistralAIChatCompletionStreamRequest, streaming: boolean ): Promise< - MistralChatCompletionResponse | AsyncIterable + MistralAIChatCompletionResponse | AsyncIterable > { const client = new MistralClient({ apiKey: this.apiKey, serverURL: this.serverURL, // If httpClient exists, pass it into constructor - ...( this.httpClient ? {httpCLient: this.httpClient} : {}) + ...( this.httpClient ? {httpClient: this.httpClient} : {}) }); return this.caller.call(async () => { try { let res: - | MistralChatCompletionResponse + | MistralAIChatCompletionResponse | AsyncIterable; if (streaming) { res = await client.chat.stream(input); diff --git a/libs/langchain-mistralai/src/embeddings.ts b/libs/langchain-mistralai/src/embeddings.ts index 5faefc35ac69..6b10f9b7d654 100644 --- a/libs/langchain-mistralai/src/embeddings.ts +++ b/libs/langchain-mistralai/src/embeddings.ts @@ -2,8 +2,8 @@ import { getEnvironmentVariable } from "@langchain/core/utils/env"; import { Embeddings, type EmbeddingsParams } from "@langchain/core/embeddings"; import { chunkArray } from "@langchain/core/utils/chunk_array"; import { EmbeddingRequest as MistralAIEmbeddingsRequest} from "@mistralai/mistralai/src/models/components/embeddingrequest.js"; -import { EmbeddingResponse as MistralAIEmbeddingsResult} from "@mistralai/mistralai/src/models/components/embeddingresponse.js"; -import { HTTPClient } from "@mistralai/mistralai/lib/http.js"; +import { EmbeddingResponse as MistralAIEmbeddingsResponse} from "@mistralai/mistralai/src/models/components/embeddingresponse.js"; +import { HTTPClient as MistralAIHTTPClient} from "@mistralai/mistralai/lib/http.js"; /** * Interface for MistralAIEmbeddings parameters. Extends EmbeddingsParams and @@ -50,7 +50,7 @@ export interface MistralAIEmbeddingsParams extends EmbeddingsParams { * Optional custom HTTP client to manage API requests * Allows users to add custom fetch implementations, hooks, as well as error and response processing. */ - httpCLient?: HTTPClient; + httpClient?: MistralAIHTTPClient; } @@ -75,7 +75,7 @@ export class MistralAIEmbeddings serverURL?: string; - httpClient?: HTTPClient; + httpClient?: MistralAIHTTPClient; constructor(fields?: Partial) { super(fields ?? {}); @@ -90,7 +90,7 @@ export class MistralAIEmbeddings this.encodingFormat = fields?.encodingFormat ?? this.encodingFormat; this.batchSize = fields?.batchSize ?? this.batchSize; this.stripNewLines = fields?.stripNewLines ?? this.stripNewLines; - this.httpClient = fields?.httpCLient ?? undefined; + this.httpClient = fields?.httpClient ?? undefined; } /** @@ -140,17 +140,17 @@ export class MistralAIEmbeddings * embeddings. Handles the retry logic and returns the response from the * API. * @param {string | Array} inputs Text to send to the MistralAI API. - * @returns {Promise} Promise that resolves to the response from the API. + * @returns {Promise} Promise that resolves to the response from the API. */ private async embeddingWithRetry( inputs: string | Array - ): Promise { + ): Promise { const { Mistral } = await this.imports(); const client = new Mistral({ apiKey: this.apiKey, serverURL: this.serverURL, // If httpClient exists, pass it into constructor - ...( this.httpClient ? {httpCLient: this.httpClient} : {}) + ...( this.httpClient ? {httpClient: this.httpClient} : {}) }); let embeddingsRequest: MistralAIEmbeddingsRequest = { model: this.model, diff --git a/libs/langchain-mistralai/src/llms.ts b/libs/langchain-mistralai/src/llms.ts index 3e4e1529864d..48b300ceb816 100644 --- a/libs/langchain-mistralai/src/llms.ts +++ b/libs/langchain-mistralai/src/llms.ts @@ -2,12 +2,12 @@ import { CallbackManagerForLLMRun } from "@langchain/core/callbacks/manager"; import { BaseLLMParams, LLM } from "@langchain/core/language_models/llms"; import { type BaseLanguageModelCallOptions } from "@langchain/core/language_models/base"; import { GenerationChunk, LLMResult } from "@langchain/core/outputs"; -import { FIMCompletionRequest as MistralFIMCompletionRequest } from "@mistralai/mistralai/models/components/fimcompletionrequest.js"; -import { FIMCompletionStreamRequest as MistralFIMCompletionStreamRequest} from "@mistralai/mistralai/models/components/fimcompletionstreamrequest.js"; -import { FIMCompletionResponse as MistralFIMCompletionResponse } from "@mistralai/mistralai/models/components/fimcompletionresponse.js"; -import { ChatCompletionChoice as MistralChatCompletionChoice} from "@mistralai/mistralai/models/components/chatcompletionchoice.js"; -import { CompletionEvent as MistralChatCompletionEvent } from "@mistralai/mistralai/models/components/completionevent.js"; -import { HTTPClient } from "@mistralai/mistralai/lib/http.js"; +import { FIMCompletionRequest as MistralAIFIMCompletionRequest } from "@mistralai/mistralai/models/components/fimcompletionrequest.js"; +import { FIMCompletionStreamRequest as MistralAIFIMCompletionStreamRequest} from "@mistralai/mistralai/models/components/fimcompletionstreamrequest.js"; +import { FIMCompletionResponse as MistralAIFIMCompletionResponse } from "@mistralai/mistralai/models/components/fimcompletionresponse.js"; +import { ChatCompletionChoice as MistralAIChatCompletionChoice} from "@mistralai/mistralai/models/components/chatcompletionchoice.js"; +import { CompletionEvent as MistralAIChatCompletionEvent } from "@mistralai/mistralai/models/components/completionevent.js"; +import { HTTPClient as MistralAIHTTPClient} from "@mistralai/mistralai/lib/http.js"; import { getEnvironmentVariable } from "@langchain/core/utils/env"; import { chunkArray } from "@langchain/core/utils/chunk_array"; import { AsyncCaller } from "@langchain/core/utils/async_caller"; @@ -73,7 +73,7 @@ export interface MistralAIInput extends BaseLLMParams { * Optional custom HTTP client to manage API requests * Allows users to add custom fetch implementations, hooks, as well as error and response processing. */ - httpClient?: HTTPClient; + httpClient?: MistralAIHTTPClient; } /** @@ -109,7 +109,7 @@ export class MistralAI maxConcurrency?: number; - httpClient?: HTTPClient; + httpClient?: MistralAIHTTPClient; constructor(fields?: MistralAIInput) { super(fields ?? {}); @@ -148,7 +148,7 @@ Either provide one via the "apiKey" field in the constructor, or set the "MISTRA invocationParams( options: this["ParsedCallOptions"] - ): Omit { + ): Omit { return { model: this.model, suffix: options.suffix, @@ -184,7 +184,7 @@ Either provide one via the "apiKey" field in the constructor, or set the "MISTRA runManager?: CallbackManagerForLLMRun ): Promise { const subPrompts = chunkArray(prompts, this.batchSize); - const choices: MistralChatCompletionChoice[][] = []; + const choices: MistralAIChatCompletionChoice[][] = []; const params = this.invocationParams(options); @@ -192,14 +192,14 @@ Either provide one via the "apiKey" field in the constructor, or set the "MISTRA const data = await (async () => { if (this.streaming) { const responseData: Array< - { choices: MistralChatCompletionChoice[] } & Partial< - Omit + { choices: MistralAIChatCompletionChoice[] } & Partial< + Omit > > = []; for (let x = 0; x < subPrompts[i].length; x += 1) { - const choices: MistralChatCompletionChoice[] = []; + const choices: MistralAIChatCompletionChoice[] = []; let response: - | Omit + | Omit | undefined; const stream = await this.completionWithRetry( { @@ -253,7 +253,7 @@ Either provide one via the "apiKey" field in the constructor, or set the "MISTRA } return responseData; } else { - const responseData: Array = []; + const responseData: Array = []; for (let x = 0; x < subPrompts[i].length; x += 1) { const res = await this.completionWithRetry( { @@ -286,23 +286,23 @@ Either provide one via the "apiKey" field in the constructor, or set the "MISTRA } async completionWithRetry( - request: MistralFIMCompletionRequest, + request: MistralAIFIMCompletionRequest, options: this["ParsedCallOptions"], stream: false - ): Promise; + ): Promise; async completionWithRetry( - request: MistralFIMCompletionStreamRequest, + request: MistralAIFIMCompletionStreamRequest, options: this["ParsedCallOptions"], stream: true - ): Promise>; + ): Promise>; async completionWithRetry( - request: MistralFIMCompletionRequest | MistralFIMCompletionStreamRequest, + request: MistralAIFIMCompletionRequest | MistralAIFIMCompletionStreamRequest, options: this["ParsedCallOptions"], stream: boolean ): Promise< - MistralFIMCompletionResponse | AsyncIterable + MistralAIFIMCompletionResponse | AsyncIterable > { const { Mistral } = await this.imports(); const caller = new AsyncCaller({ @@ -314,7 +314,7 @@ Either provide one via the "apiKey" field in the constructor, or set the "MISTRA serverURL: this.serverURL, timeoutMs: options.timeout, // If httpClient exists, pass it into constructor - ...( this.httpClient ? {httpCLient: this.httpClient} : {}) + ...( this.httpClient ? {httpClient: this.httpClient} : {}) }); return caller.callWithOptions( { @@ -323,8 +323,8 @@ Either provide one via the "apiKey" field in the constructor, or set the "MISTRA async () => { try { let res: - | MistralFIMCompletionResponse - | AsyncIterable; + | MistralAIFIMCompletionResponse + | AsyncIterable; if (stream) { res = await client.fim.stream(request); } else { From 2a759083c037ef259b73af0254c61c1794ed0148 Mon Sep 17 00:00:00 2001 From: CarterMorris Date: Wed, 6 Nov 2024 17:03:28 -0500 Subject: [PATCH 009/344] Experimental addition of Mistral retry config for chat_models.ts --- libs/langchain-mistralai/src/chat_models.ts | 96 ++++++++++++++++++--- 1 file changed, 84 insertions(+), 12 deletions(-) diff --git a/libs/langchain-mistralai/src/chat_models.ts b/libs/langchain-mistralai/src/chat_models.ts index 0b7dfa4d7724..c2a6fd3693ec 100644 --- a/libs/langchain-mistralai/src/chat_models.ts +++ b/libs/langchain-mistralai/src/chat_models.ts @@ -13,6 +13,7 @@ import { UsageInfo as MistralAITokenUsage } from "@mistralai/mistralai/models/co import { CompletionEvent as MistralAIChatCompletionEvent } from "@mistralai/mistralai/models/components/completionevent.js"; import { ChatCompletionResponse as MistralAIChatCompletionResponse } from "@mistralai/mistralai/models/components/chatcompletionresponse.js"; import { HTTPClient as MistralAIHTTPClient} from "@mistralai/mistralai/lib/http.js"; +import { RetryConfig as MistralAIRetryConfig } from "@mistralai/mistralai/lib/retries.js"; import { MessageType, type BaseMessage, @@ -162,10 +163,45 @@ export interface ChatMistralAIInput */ seed?: number; /** - * + * Custom HTTP client to manage API requests + * Allows users to add custom fetch implementations, hooks, as well as error and response processing. + */ + httpClient?: MistralAIHTTPClient; + /** + * The strategy for handling request errors. Either "none" or "backoff". + * @default {"none"} + */ + backoffStrategy?: string; + /** + * The intial time interval to wait before retrying a failed request, in ms. + * Only used when backoffStrategy = "backoff". + * @default {500} + */ + backoffInitialInterval?: number; + /** + * The maximum interval of time to wait before retrying a failed request, in ms. + * Only used when backoffStrategy = "backoff". + * @default {60000} + */ + backoffMaxInterval?: number; + /** + * The base to exponentiate by the number of retries attempted. The time interval to wait for the + * next retry is backoffInitialInterval * backoffExponent**number_of_retries, in ms. + * Only used when backoffStrategy = "backoff". + * @default {1.5} + */ + backoffExponent?: number; + /** + * The maximum time to retry requests for, after the first request is received by Mistral, in ms. + * Only used when backoffStrategy = "backoff". + * @default {3600000} + */ + backoffMaxElapsedTime?: number; + /** + * Whether or not to retry requests that failed due to connection errors. + * Only used when backoffStrategy = "backoff". */ - httpClient?: MistralAIHTTPClient | undefined; - + retryConnectionErrors?: boolean; } function convertMessagesToMistralMessages( @@ -196,14 +232,14 @@ function convertMessagesToMistralMessages( return { type: complex.type, imageUrl: complex?.image_url - } as MistralAIContentChunk; + }; } - if (complex.type === "text" && (role === "user" || role === "system")){ + if (complex.type === "text" && (role === "user" || role === "system")) { return { type: complex.type, text: complex?.text - } as MistralAIContentChunk; + }; } throw new Error( @@ -817,12 +853,20 @@ export class ChatMistralAI< streamUsage = true; - /** - * Optional custom HTTP client to manage API requests - * Allows users to add custom fetch implementations, hooks, as well as error and response processing. - */ httpClient?: MistralAIHTTPClient; + backoffStrategy = "none"; + + backoffInitialInterval = 500; + + backoffMaxInterval = 60000; + + backoffExponent = 1.5; + + backoffMaxElapsedTime = 3600000; + + retryConnectionErrors?: boolean; + constructor(fields?: ChatMistralAIInput) { super(fields ?? {}); const apiKey = fields?.apiKey ?? getEnvironmentVariable("MISTRAL_API_KEY"); @@ -843,7 +887,13 @@ export class ChatMistralAI< this.modelName = fields?.model ?? fields?.modelName ?? this.model; this.model = this.modelName; this.streamUsage = fields?.streamUsage ?? this.streamUsage; - this.httpClient = fields?.httpClient ?? undefined; + this.httpClient = fields?.httpClient ?? this.httpClient; + this.backoffStrategy = fields?.backoffStrategy ?? this.backoffStrategy; + this.backoffInitialInterval = fields?.backoffInitialInterval ?? this.backoffInitialInterval; + this.backoffMaxInterval = fields?.backoffMaxInterval ?? this.backoffMaxInterval; + this.backoffExponent = fields?.backoffExponent ?? this.backoffExponent; + this.backoffMaxElapsedTime = fields?.backoffMaxElapsedTime ?? this.backoffMaxElapsedTime; + this.retryConnectionErrors = fields?.retryConnectionErrors ?? this.retryConnectionErrors; } getLsParams(options: this["ParsedCallOptions"]): LangSmithParams { @@ -916,11 +966,33 @@ export class ChatMistralAI< ): Promise< MistralAIChatCompletionResponse | AsyncIterable > { + /** + * Get the Mistral request retry config + */ + const _getRetryConfig = (): MistralAIRetryConfig => { + if (this.backoffStrategy === "backoff") { + return { + strategy: this.backoffStrategy, + backoff: { + initialInterval: this.backoffInitialInterval, + maxInterval: this.backoffMaxInterval, + exponent: this.backoffExponent, + maxElapsedTime: this.backoffMaxElapsedTime, + }, + retryConnectionErrors: this.retryConnectionErrors + }; + } + return { + strategy: "none" + }; + }; + const client = new MistralClient({ apiKey: this.apiKey, serverURL: this.serverURL, // If httpClient exists, pass it into constructor - ...( this.httpClient ? {httpClient: this.httpClient} : {}) + ...( this.httpClient ? {httpClient: this.httpClient} : {}), + retryConfig: _getRetryConfig(), }); return this.caller.call(async () => { From 0d778c9744b631dc8a132c551199b6354c6a9ade Mon Sep 17 00:00:00 2001 From: BaNg-W Date: Thu, 7 Nov 2024 04:32:35 -0500 Subject: [PATCH 010/344] Fixed zod not being resolved --- libs/langchain-mistralai/tsconfig.json | 2 +- package.json | 3 +++ yarn.lock | 18 +++++------------- 3 files changed, 9 insertions(+), 14 deletions(-) diff --git a/libs/langchain-mistralai/tsconfig.json b/libs/langchain-mistralai/tsconfig.json index ffc49dde54a6..05cdf3a4d72b 100644 --- a/libs/langchain-mistralai/tsconfig.json +++ b/libs/langchain-mistralai/tsconfig.json @@ -9,7 +9,7 @@ "ES2022.Object", "DOM" ], - "module": "ES2020", + "module": "NodeNext", "moduleResolution": "nodenext", "esModuleInterop": true, "declaration": true, diff --git a/package.json b/package.json index d6161145a605..4bcab88db719 100644 --- a/package.json +++ b/package.json @@ -72,5 +72,8 @@ "eslint --cache --fix" ], "*.md": "prettier --config .prettierrc --write" + }, + "dependencies": { + "zod": "^3.23.8" } } diff --git a/yarn.lock b/yarn.lock index 18b9eab032c7..178962cc8fc0 100644 --- a/yarn.lock +++ b/yarn.lock @@ -12724,21 +12724,12 @@ __metadata: languageName: node linkType: hard -"@mistralai/mistralai@npm:^0.4.0": - version: 0.4.0 - resolution: "@mistralai/mistralai@npm:0.4.0" - dependencies: - node-fetch: ^2.6.7 - checksum: 1b03fc0b55164c02e5fb29fb2d09ebe4ad44346fc313f7fb3ab09e48f73f975763d1ac9654098d433ea17d7caa20654b2b15510822276acc9fa46db461a254a6 - languageName: node - linkType: hard - -"@mistralai/mistralai@npm:^1.0.2": - version: 1.0.2 - resolution: "@mistralai/mistralai@npm:1.0.2" +"@mistralai/mistralai@npm:^1.1.0": + version: 1.1.0 + resolution: "@mistralai/mistralai@npm:1.1.0" peerDependencies: zod: ">= 3" - checksum: 9d2ed8d96d20791571cf9ad2a47c4fb2a8a991543ce95a7daeba8f146cca73cac8e05b9da1e96b16d84a08c3c9bcea90c3e4909ce55dd366ea4931b2e18a28bb + checksum: 219a96ec73361f7c9079d8ed77a504bea767fba7e150159d6c7ec1019b5a0a76eaf0a867b5562442ed5face483a2b7c24f597bde56ecf7b39aece3b36831ce79 languageName: node linkType: hard @@ -32344,6 +32335,7 @@ __metadata: semver: ^7.5.4 turbo: ^1.13.3 typescript: ~5.1.6 + zod: ^3.23.8 languageName: unknown linkType: soft From a6eb0b3d6ff6f27b07e8b4b290b5728041c84127 Mon Sep 17 00:00:00 2001 From: CarterMorris Date: Thu, 7 Nov 2024 14:51:21 -0500 Subject: [PATCH 011/344] Removed global zod dependency from previous commit (0d778c9) --- package.json | 3 --- yarn.lock | 1 - 2 files changed, 4 deletions(-) diff --git a/package.json b/package.json index 4bcab88db719..d6161145a605 100644 --- a/package.json +++ b/package.json @@ -72,8 +72,5 @@ "eslint --cache --fix" ], "*.md": "prettier --config .prettierrc --write" - }, - "dependencies": { - "zod": "^3.23.8" } } diff --git a/yarn.lock b/yarn.lock index 178962cc8fc0..036030d4dab3 100644 --- a/yarn.lock +++ b/yarn.lock @@ -32335,7 +32335,6 @@ __metadata: semver: ^7.5.4 turbo: ^1.13.3 typescript: ~5.1.6 - zod: ^3.23.8 languageName: unknown linkType: soft From 2db1dd4e2fe80bce377a20a2ac3b1e1c2392f158 Mon Sep 17 00:00:00 2001 From: Ashtian Date: Sat, 9 Nov 2024 11:59:43 -0500 Subject: [PATCH 012/344] streamlined adding and removing hooks for httpclient --- libs/langchain-mistralai/src/chat_models.ts | 103 ++++++++++++++++++- libs/langchain-mistralai/src/embeddings.ts | 106 ++++++++++++++++++- libs/langchain-mistralai/src/llms.ts | 107 +++++++++++++++++++- 3 files changed, 311 insertions(+), 5 deletions(-) diff --git a/libs/langchain-mistralai/src/chat_models.ts b/libs/langchain-mistralai/src/chat_models.ts index c2a6fd3693ec..bbaa05521396 100644 --- a/libs/langchain-mistralai/src/chat_models.ts +++ b/libs/langchain-mistralai/src/chat_models.ts @@ -12,7 +12,7 @@ import { ChatCompletionStreamRequest as MistralAIChatCompletionStreamRequest } f import { UsageInfo as MistralAITokenUsage } from "@mistralai/mistralai/models/components/usageinfo.js"; import { CompletionEvent as MistralAIChatCompletionEvent } from "@mistralai/mistralai/models/components/completionevent.js"; import { ChatCompletionResponse as MistralAIChatCompletionResponse } from "@mistralai/mistralai/models/components/chatcompletionresponse.js"; -import { HTTPClient as MistralAIHTTPClient} from "@mistralai/mistralai/lib/http.js"; +import { BeforeRequestHook, HTTPClient as MistralAIHTTPClient, RequestErrorHook, ResponseHook} from "@mistralai/mistralai/lib/http.js"; import { RetryConfig as MistralAIRetryConfig } from "@mistralai/mistralai/lib/retries.js"; import { MessageType, @@ -162,6 +162,24 @@ export interface ChatMistralAIInput * The seed to use for random sampling. If set, different calls will generate deterministic results. */ seed?: number; + /** + * A list of custom hooks that must follow (req: Request) => Awaitable + * They are automatically added when a ChatMistralAI Object is created + * @default {[]} + */ + beforeRequestHooks?: Array; + /** + * A list of custom hooks that must follow (err: unknown, req: Request) => Awaitable + * They are automatically added when a ChatMistralAI Object is created + * @default {[]} + */ + requestErrorHooks?: Array; + /** + * A list of custom hooks that must follow (res: Response, req: Request) => Awaitable + * They are automatically added when a ChatMistralAI Object is created + * @default {[]} + */ + responseHooks?: Array; /** * Custom HTTP client to manage API requests * Allows users to add custom fetch implementations, hooks, as well as error and response processing. @@ -853,6 +871,12 @@ export class ChatMistralAI< streamUsage = true; + beforeRequestHooks?: Array; + + requestErrorHooks?: Array; + + responseHooks?: Array; + httpClient?: MistralAIHTTPClient; backoffStrategy = "none"; @@ -887,6 +911,9 @@ export class ChatMistralAI< this.modelName = fields?.model ?? fields?.modelName ?? this.model; this.model = this.modelName; this.streamUsage = fields?.streamUsage ?? this.streamUsage; + this.beforeRequestHooks = fields?.beforeRequestHooks ?? this.beforeRequestHooks; + this.requestErrorHooks = fields?.requestErrorHooks ?? this.requestErrorHooks; + this.responseHooks = fields?.responseHooks ?? this.responseHooks; this.httpClient = fields?.httpClient ?? this.httpClient; this.backoffStrategy = fields?.backoffStrategy ?? this.backoffStrategy; this.backoffInitialInterval = fields?.backoffInitialInterval ?? this.backoffInitialInterval; @@ -894,6 +921,7 @@ export class ChatMistralAI< this.backoffExponent = fields?.backoffExponent ?? this.backoffExponent; this.backoffMaxElapsedTime = fields?.backoffMaxElapsedTime ?? this.backoffMaxElapsedTime; this.retryConnectionErrors = fields?.retryConnectionErrors ?? this.retryConnectionErrors; + this.addAllHooksToHttpClient(); } getLsParams(options: this["ParsedCallOptions"]): LangSmithParams { @@ -1159,6 +1187,79 @@ export class ChatMistralAI< } } + addAllHooksToHttpClient() { + try { + // To prevent duplicate hooks + this.removeAllHooksFromHttpClient(); + + // If the user wants to use hooks, but hasn't created an HTTPClient yet + const hasHooks = [ + this.beforeRequestHooks, + this.requestErrorHooks, + this.responseHooks + ].some(hook => hook && hook.length > 0); + if(hasHooks && !this.httpClient) { + this.httpClient = new MistralAIHTTPClient(); + } + + if(this.beforeRequestHooks) { + for(const hook of this.beforeRequestHooks) { + this.httpClient?.addHook("beforeRequest", hook); + } + } + + if(this.requestErrorHooks) { + for(const hook of this.requestErrorHooks) { + this.httpClient?.addHook("requestError", hook); + } + } + + if(this.responseHooks) { + for(const hook of this.responseHooks) { + this.httpClient?.addHook("response", hook); + } + } + } catch { + throw new Error("Error in adding all hooks"); + } + } + + removeAllHooksFromHttpClient() { + try { + if(this.beforeRequestHooks) { + for(const hook of this.beforeRequestHooks) { + this.httpClient?.removeHook("beforeRequest", hook); + } + } + + if(this.requestErrorHooks) { + for(const hook of this.requestErrorHooks) { + this.httpClient?.removeHook("requestError", hook); + } + } + + if(this.responseHooks) { + for(const hook of this.responseHooks) { + this.httpClient?.removeHook("response", hook); + } + } + } catch { + throw new Error("Error in removing hooks"); + } + } + + removeHookFromHttpClient( + hook: BeforeRequestHook | RequestErrorHook | ResponseHook + ) { + try { + this.httpClient?.removeHook("beforeRequest", hook as BeforeRequestHook); + this.httpClient?.removeHook("requestError", hook as RequestErrorHook); + this.httpClient?.removeHook("response", hook as ResponseHook); + } catch { + throw new Error("Error in removing hook"); + } + } + /** @ignore */ _combineLLMOutput() { return []; diff --git a/libs/langchain-mistralai/src/embeddings.ts b/libs/langchain-mistralai/src/embeddings.ts index 6b10f9b7d654..6e872cd6a8e2 100644 --- a/libs/langchain-mistralai/src/embeddings.ts +++ b/libs/langchain-mistralai/src/embeddings.ts @@ -3,7 +3,7 @@ import { Embeddings, type EmbeddingsParams } from "@langchain/core/embeddings"; import { chunkArray } from "@langchain/core/utils/chunk_array"; import { EmbeddingRequest as MistralAIEmbeddingsRequest} from "@mistralai/mistralai/src/models/components/embeddingrequest.js"; import { EmbeddingResponse as MistralAIEmbeddingsResponse} from "@mistralai/mistralai/src/models/components/embeddingresponse.js"; -import { HTTPClient as MistralAIHTTPClient} from "@mistralai/mistralai/lib/http.js"; +import { BeforeRequestHook, HTTPClient as MistralAIHTTPClient, RequestErrorHook, ResponseHook} from "@mistralai/mistralai/lib/http.js"; /** * Interface for MistralAIEmbeddings parameters. Extends EmbeddingsParams and @@ -46,6 +46,24 @@ export interface MistralAIEmbeddingsParams extends EmbeddingsParams { * @default {true} */ stripNewLines?: boolean; + /** + * A list of custom hooks that must follow (req: Request) => Awaitable + * They are automatically added when a ChatMistralAI Object is created + * @default {[]} + */ + beforeRequestHooks?: Array; + /** + * A list of custom hooks that must follow (err: unknown, req: Request) => Awaitable + * They are automatically added when a ChatMistralAI Object is created + * @default {[]} + */ + requestErrorHooks?: Array; + /** + * A list of custom hooks that must follow (res: Response, req: Request) => Awaitable + * They are automatically added when a ChatMistralAI Object is created + * @default {[]} + */ + responseHooks?: Array; /** * Optional custom HTTP client to manage API requests * Allows users to add custom fetch implementations, hooks, as well as error and response processing. @@ -75,6 +93,12 @@ export class MistralAIEmbeddings serverURL?: string; + beforeRequestHooks?: Array; + + requestErrorHooks?: Array; + + responseHooks?: Array; + httpClient?: MistralAIHTTPClient; constructor(fields?: Partial) { @@ -90,9 +114,14 @@ export class MistralAIEmbeddings this.encodingFormat = fields?.encodingFormat ?? this.encodingFormat; this.batchSize = fields?.batchSize ?? this.batchSize; this.stripNewLines = fields?.stripNewLines ?? this.stripNewLines; - this.httpClient = fields?.httpClient ?? undefined; + this.beforeRequestHooks = fields?.beforeRequestHooks ?? this.beforeRequestHooks; + this.requestErrorHooks = fields?.requestErrorHooks ?? this.requestErrorHooks; + this.responseHooks = fields?.responseHooks ?? this.responseHooks; + this.httpClient = fields?.httpClient ?? this.httpClient; + this.addAllHooksToHttpClient(); } + /** * Method to generate embeddings for an array of documents. Splits the * documents into batches and makes requests to the MistralAI API to generate @@ -166,6 +195,79 @@ export class MistralAIEmbeddings }); } + addAllHooksToHttpClient() { + try { + // To prevent duplicate hooks + this.removeAllHooksFromHttpClient(); + + // If the user wants to use hooks, but hasn't created an HTTPClient yet + const hasHooks = [ + this.beforeRequestHooks, + this.requestErrorHooks, + this.responseHooks + ].some(hook => hook && hook.length > 0); + if(hasHooks && !this.httpClient) { + this.httpClient = new MistralAIHTTPClient(); + } + + if(this.beforeRequestHooks) { + for(const hook of this.beforeRequestHooks) { + this.httpClient?.addHook("beforeRequest", hook); + } + } + + if(this.requestErrorHooks) { + for(const hook of this.requestErrorHooks) { + this.httpClient?.addHook("requestError", hook); + } + } + + if(this.responseHooks) { + for(const hook of this.responseHooks) { + this.httpClient?.addHook("response", hook); + } + } + } catch { + throw new Error("Error in adding all hooks"); + } + } + + removeAllHooksFromHttpClient() { + try { + if(this.beforeRequestHooks) { + for(const hook of this.beforeRequestHooks) { + this.httpClient?.removeHook("beforeRequest", hook); + } + } + + if(this.requestErrorHooks) { + for(const hook of this.requestErrorHooks) { + this.httpClient?.removeHook("requestError", hook); + } + } + + if(this.responseHooks) { + for(const hook of this.responseHooks) { + this.httpClient?.removeHook("response", hook); + } + } + } catch { + throw new Error("Error in removing hooks"); + } + } + + removeHookFromHttpClient( + hook: BeforeRequestHook | RequestErrorHook | ResponseHook + ) { + try { + this.httpClient?.removeHook("beforeRequest", hook as BeforeRequestHook); + this.httpClient?.removeHook("requestError", hook as RequestErrorHook); + this.httpClient?.removeHook("response", hook as ResponseHook); + } catch { + throw new Error("Error in removing hook"); + } + } + /** @ignore */ private async imports() { const { Mistral } = await import("@mistralai/mistralai"); diff --git a/libs/langchain-mistralai/src/llms.ts b/libs/langchain-mistralai/src/llms.ts index 48b300ceb816..905e55e72cc4 100644 --- a/libs/langchain-mistralai/src/llms.ts +++ b/libs/langchain-mistralai/src/llms.ts @@ -7,7 +7,7 @@ import { FIMCompletionStreamRequest as MistralAIFIMCompletionStreamRequest} from import { FIMCompletionResponse as MistralAIFIMCompletionResponse } from "@mistralai/mistralai/models/components/fimcompletionresponse.js"; import { ChatCompletionChoice as MistralAIChatCompletionChoice} from "@mistralai/mistralai/models/components/chatcompletionchoice.js"; import { CompletionEvent as MistralAIChatCompletionEvent } from "@mistralai/mistralai/models/components/completionevent.js"; -import { HTTPClient as MistralAIHTTPClient} from "@mistralai/mistralai/lib/http.js"; +import { BeforeRequestHook, HTTPClient as MistralAIHTTPClient, RequestErrorHook, ResponseHook} from "@mistralai/mistralai/lib/http.js"; import { getEnvironmentVariable } from "@langchain/core/utils/env"; import { chunkArray } from "@langchain/core/utils/chunk_array"; import { AsyncCaller } from "@langchain/core/utils/async_caller"; @@ -69,6 +69,24 @@ export interface MistralAIInput extends BaseLLMParams { * Batch size to use when passing multiple documents to generate */ batchSize?: number; + /** + * A list of custom hooks that must follow (req: Request) => Awaitable + * They are automatically added when a ChatMistralAI Object is created + * @default {[]} + */ + beforeRequestHooks?: Array; + /** + * A list of custom hooks that must follow (err: unknown, req: Request) => Awaitable + * They are automatically added when a ChatMistralAI Object is created + * @default {[]} + */ + requestErrorHooks?: Array; + /** + * A list of custom hooks that must follow (res: Response, req: Request) => Awaitable + * They are automatically added when a ChatMistralAI Object is created + * @default {[]} + */ + responseHooks?: Array; /** * Optional custom HTTP client to manage API requests * Allows users to add custom fetch implementations, hooks, as well as error and response processing. @@ -109,6 +127,12 @@ export class MistralAI maxConcurrency?: number; + beforeRequestHooks?: Array; + + requestErrorHooks?: Array; + + responseHooks?: Array; + httpClient?: MistralAIHTTPClient; constructor(fields?: MistralAIInput) { @@ -124,7 +148,10 @@ export class MistralAI this.serverURL = fields?.serverURL; this.maxRetries = fields?.maxRetries; this.maxConcurrency = fields?.maxConcurrency; - this.httpClient = fields?.httpClient ?? undefined; + this.beforeRequestHooks = fields?.beforeRequestHooks ?? this.beforeRequestHooks; + this.requestErrorHooks = fields?.requestErrorHooks ?? this.requestErrorHooks; + this.responseHooks = fields?.responseHooks ?? this.responseHooks; + this.httpClient = fields?.httpClient ?? this.httpClient; const apiKey = fields?.apiKey ?? getEnvironmentVariable("MISTRAL_API_KEY"); if (!apiKey) { @@ -134,6 +161,8 @@ Either provide one via the "apiKey" field in the constructor, or set the "MISTRA ); } this.apiKey = apiKey; + + this.addAllHooksToHttpClient(); } get lc_secrets(): { [key: string]: string } | undefined { @@ -379,6 +408,80 @@ Either provide one via the "apiKey" field in the constructor, or set the "MISTRA } } + addAllHooksToHttpClient() { + try { + // To prevent duplicate hooks + this.removeAllHooksFromHttpClient(); + + // If the user wants to use hooks, but hasn't created an HTTPClient yet + const hasHooks = [ + this.beforeRequestHooks, + this.requestErrorHooks, + this.responseHooks + ].some(hook => hook && hook.length > 0); + if(hasHooks && !this.httpClient) { + this.httpClient = new MistralAIHTTPClient(); + } + + if(this.beforeRequestHooks) { + for(const hook of this.beforeRequestHooks) { + this.httpClient?.addHook("beforeRequest", hook); + } + } + + if(this.requestErrorHooks) { + for(const hook of this.requestErrorHooks) { + this.httpClient?.addHook("requestError", hook); + } + } + + if(this.responseHooks) { + for(const hook of this.responseHooks) { + this.httpClient?.addHook("response", hook); + } + } + } catch { + throw new Error("Error in adding all hooks"); + } + } + + removeAllHooksFromHttpClient() { + try { + if(this.beforeRequestHooks) { + for(const hook of this.beforeRequestHooks) { + this.httpClient?.removeHook("beforeRequest", hook); + } + } + + if(this.requestErrorHooks) { + for(const hook of this.requestErrorHooks) { + this.httpClient?.removeHook("requestError", hook); + } + } + + if(this.responseHooks) { + for(const hook of this.responseHooks) { + this.httpClient?.removeHook("response", hook); + } + } + } catch { + throw new Error("Error in removing hooks"); + } + } + + removeHookFromHttpClient( + hook: BeforeRequestHook | RequestErrorHook | ResponseHook + ) { + try { + this.httpClient?.removeHook("beforeRequest", hook as BeforeRequestHook); + this.httpClient?.removeHook("requestError", hook as RequestErrorHook); + this.httpClient?.removeHook("response", hook as ResponseHook); + } catch { + throw new Error("Error in removing hook"); + } + } + + /** @ignore */ private async imports() { const { Mistral } = await import("@mistralai/mistralai"); From e941ff3a6bfffae18269baddc3770d2e02630add Mon Sep 17 00:00:00 2001 From: BaNg-W Date: Sat, 9 Nov 2024 17:09:22 -0500 Subject: [PATCH 013/344] Fixed formatting of hook fields and deprecated endpoint Co-authored-by: CarterMorris --- libs/langchain-mistralai/src/chat_models.ts | 42 +++++++++++++-------- libs/langchain-mistralai/src/embeddings.ts | 42 +++++++++++++-------- libs/langchain-mistralai/src/llms.ts | 42 +++++++++++++-------- 3 files changed, 81 insertions(+), 45 deletions(-) diff --git a/libs/langchain-mistralai/src/chat_models.ts b/libs/langchain-mistralai/src/chat_models.ts index bbaa05521396..3d80c670a94c 100644 --- a/libs/langchain-mistralai/src/chat_models.ts +++ b/libs/langchain-mistralai/src/chat_models.ts @@ -12,7 +12,12 @@ import { ChatCompletionStreamRequest as MistralAIChatCompletionStreamRequest } f import { UsageInfo as MistralAITokenUsage } from "@mistralai/mistralai/models/components/usageinfo.js"; import { CompletionEvent as MistralAIChatCompletionEvent } from "@mistralai/mistralai/models/components/completionevent.js"; import { ChatCompletionResponse as MistralAIChatCompletionResponse } from "@mistralai/mistralai/models/components/chatcompletionresponse.js"; -import { BeforeRequestHook, HTTPClient as MistralAIHTTPClient, RequestErrorHook, ResponseHook} from "@mistralai/mistralai/lib/http.js"; +import { + BeforeRequestHook, + RequestErrorHook, + ResponseHook, + HTTPClient as MistralAIHTTPClient, +} from "@mistralai/mistralai/lib/http.js"; import { RetryConfig as MistralAIRetryConfig } from "@mistralai/mistralai/lib/retries.js"; import { MessageType, @@ -115,6 +120,11 @@ export interface ChatMistralAIInput * @default {"mistral-small-latest"} */ model?: string; + /** + * Override the default server URL used by the Mistral SDK. + * @deprecated use serverURL instead + */ + endpoint?: string; /** * Override the default server URL used by the Mistral SDK. */ @@ -164,20 +174,17 @@ export interface ChatMistralAIInput seed?: number; /** * A list of custom hooks that must follow (req: Request) => Awaitable - * They are automatically added when a ChatMistralAI Object is created - * @default {[]} + * They are automatically added when a ChatMistralAI instance is created */ beforeRequestHooks?: Array; /** * A list of custom hooks that must follow (err: unknown, req: Request) => Awaitable - * They are automatically added when a ChatMistralAI Object is created - * @default {[]} + * They are automatically added when a ChatMistralAI instance is created */ requestErrorHooks?: Array; /** * A list of custom hooks that must follow (res: Response, req: Request) => Awaitable - * They are automatically added when a ChatMistralAI Object is created - * @default {[]} + * They are automatically added when a ChatMistralAI instance is created */ responseHooks?: Array; /** @@ -846,6 +853,11 @@ export class ChatMistralAI< apiKey: string; + /** + * @deprecated use serverURL instead + */ + endpoint: string; + serverURL?: string; temperature = 0.7; @@ -901,7 +913,7 @@ export class ChatMistralAI< } this.apiKey = apiKey; this.streaming = fields?.streaming ?? this.streaming; - this.serverURL = fields?.serverURL; + this.serverURL = fields?.serverURL ?? this.serverURL; this.temperature = fields?.temperature ?? this.temperature; this.topP = fields?.topP ?? this.topP; this.maxTokens = fields?.maxTokens ?? this.maxTokens; @@ -1198,23 +1210,23 @@ export class ChatMistralAI< this.requestErrorHooks, this.responseHooks ].some(hook => hook && hook.length > 0); - if(hasHooks && !this.httpClient) { + if (hasHooks && !this.httpClient) { this.httpClient = new MistralAIHTTPClient(); } - if(this.beforeRequestHooks) { + if (this.beforeRequestHooks) { for(const hook of this.beforeRequestHooks) { this.httpClient?.addHook("beforeRequest", hook); } } - if(this.requestErrorHooks) { + if (this.requestErrorHooks) { for(const hook of this.requestErrorHooks) { this.httpClient?.addHook("requestError", hook); } } - if(this.responseHooks) { + if (this.responseHooks) { for(const hook of this.responseHooks) { this.httpClient?.addHook("response", hook); } @@ -1226,19 +1238,19 @@ export class ChatMistralAI< removeAllHooksFromHttpClient() { try { - if(this.beforeRequestHooks) { + if (this.beforeRequestHooks) { for(const hook of this.beforeRequestHooks) { this.httpClient?.removeHook("beforeRequest", hook); } } - if(this.requestErrorHooks) { + if (this.requestErrorHooks) { for(const hook of this.requestErrorHooks) { this.httpClient?.removeHook("requestError", hook); } } - if(this.responseHooks) { + if (this.responseHooks) { for(const hook of this.responseHooks) { this.httpClient?.removeHook("response", hook); } diff --git a/libs/langchain-mistralai/src/embeddings.ts b/libs/langchain-mistralai/src/embeddings.ts index 6e872cd6a8e2..e4b3a1657eab 100644 --- a/libs/langchain-mistralai/src/embeddings.ts +++ b/libs/langchain-mistralai/src/embeddings.ts @@ -3,7 +3,12 @@ import { Embeddings, type EmbeddingsParams } from "@langchain/core/embeddings"; import { chunkArray } from "@langchain/core/utils/chunk_array"; import { EmbeddingRequest as MistralAIEmbeddingsRequest} from "@mistralai/mistralai/src/models/components/embeddingrequest.js"; import { EmbeddingResponse as MistralAIEmbeddingsResponse} from "@mistralai/mistralai/src/models/components/embeddingresponse.js"; -import { BeforeRequestHook, HTTPClient as MistralAIHTTPClient, RequestErrorHook, ResponseHook} from "@mistralai/mistralai/lib/http.js"; +import { + BeforeRequestHook, + RequestErrorHook, + ResponseHook, + HTTPClient as MistralAIHTTPClient, +} from "@mistralai/mistralai/lib/http.js"; /** * Interface for MistralAIEmbeddings parameters. Extends EmbeddingsParams and @@ -31,6 +36,11 @@ export interface MistralAIEmbeddingsParams extends EmbeddingsParams { * @default {"float"} */ encodingFormat?: string; + /** + * Override the default server URL used by the Mistral SDK. + * @deprecated use serverURL instead + */ + endpoint?: string; /** * Override the default server URL used by the Mistral SDK. */ @@ -48,20 +58,17 @@ export interface MistralAIEmbeddingsParams extends EmbeddingsParams { stripNewLines?: boolean; /** * A list of custom hooks that must follow (req: Request) => Awaitable - * They are automatically added when a ChatMistralAI Object is created - * @default {[]} + * They are automatically added when a ChatMistralAI instance is created */ beforeRequestHooks?: Array; /** * A list of custom hooks that must follow (err: unknown, req: Request) => Awaitable - * They are automatically added when a ChatMistralAI Object is created - * @default {[]} + * They are automatically added when a ChatMistralAI instance is created */ requestErrorHooks?: Array; /** * A list of custom hooks that must follow (res: Response, req: Request) => Awaitable - * They are automatically added when a ChatMistralAI Object is created - * @default {[]} + * They are automatically added when a ChatMistralAI instance is created */ responseHooks?: Array; /** @@ -91,6 +98,11 @@ export class MistralAIEmbeddings apiKey: string; + /** + * @deprecated use serverURL instead + */ + endpoint: string; + serverURL?: string; beforeRequestHooks?: Array; @@ -108,7 +120,7 @@ export class MistralAIEmbeddings throw new Error("API key missing for MistralAI, but it is required."); } this.apiKey = apiKey; - this.serverURL = fields?.serverURL; + this.serverURL = fields?.serverURL ?? this.serverURL; this.modelName = fields?.model ?? fields?.modelName ?? this.model; this.model = this.modelName; this.encodingFormat = fields?.encodingFormat ?? this.encodingFormat; @@ -206,23 +218,23 @@ export class MistralAIEmbeddings this.requestErrorHooks, this.responseHooks ].some(hook => hook && hook.length > 0); - if(hasHooks && !this.httpClient) { + if (hasHooks && !this.httpClient) { this.httpClient = new MistralAIHTTPClient(); } - if(this.beforeRequestHooks) { + if (this.beforeRequestHooks) { for(const hook of this.beforeRequestHooks) { this.httpClient?.addHook("beforeRequest", hook); } } - if(this.requestErrorHooks) { + if (this.requestErrorHooks) { for(const hook of this.requestErrorHooks) { this.httpClient?.addHook("requestError", hook); } } - if(this.responseHooks) { + if (this.responseHooks) { for(const hook of this.responseHooks) { this.httpClient?.addHook("response", hook); } @@ -234,19 +246,19 @@ export class MistralAIEmbeddings removeAllHooksFromHttpClient() { try { - if(this.beforeRequestHooks) { + if (this.beforeRequestHooks) { for(const hook of this.beforeRequestHooks) { this.httpClient?.removeHook("beforeRequest", hook); } } - if(this.requestErrorHooks) { + if (this.requestErrorHooks) { for(const hook of this.requestErrorHooks) { this.httpClient?.removeHook("requestError", hook); } } - if(this.responseHooks) { + if (this.responseHooks) { for(const hook of this.responseHooks) { this.httpClient?.removeHook("response", hook); } diff --git a/libs/langchain-mistralai/src/llms.ts b/libs/langchain-mistralai/src/llms.ts index 905e55e72cc4..a0ff711f358c 100644 --- a/libs/langchain-mistralai/src/llms.ts +++ b/libs/langchain-mistralai/src/llms.ts @@ -7,7 +7,12 @@ import { FIMCompletionStreamRequest as MistralAIFIMCompletionStreamRequest} from import { FIMCompletionResponse as MistralAIFIMCompletionResponse } from "@mistralai/mistralai/models/components/fimcompletionresponse.js"; import { ChatCompletionChoice as MistralAIChatCompletionChoice} from "@mistralai/mistralai/models/components/chatcompletionchoice.js"; import { CompletionEvent as MistralAIChatCompletionEvent } from "@mistralai/mistralai/models/components/completionevent.js"; -import { BeforeRequestHook, HTTPClient as MistralAIHTTPClient, RequestErrorHook, ResponseHook} from "@mistralai/mistralai/lib/http.js"; +import { + BeforeRequestHook, + RequestErrorHook, + ResponseHook, + HTTPClient as MistralAIHTTPClient, +} from "@mistralai/mistralai/lib/http.js"; import { getEnvironmentVariable } from "@langchain/core/utils/env"; import { chunkArray } from "@langchain/core/utils/chunk_array"; import { AsyncCaller } from "@langchain/core/utils/async_caller"; @@ -33,6 +38,11 @@ export interface MistralAIInput extends BaseLLMParams { * @default {process.env.MISTRAL_API_KEY} */ apiKey?: string; + /** + * Override the default server URL used by the Mistral SDK. + * @deprecated use serverURL instead + */ + endpoint?: string; /** * Override the default server URL used by the Mistral SDK. */ @@ -71,20 +81,17 @@ export interface MistralAIInput extends BaseLLMParams { batchSize?: number; /** * A list of custom hooks that must follow (req: Request) => Awaitable - * They are automatically added when a ChatMistralAI Object is created - * @default {[]} + * They are automatically added when a ChatMistralAI instance is created */ beforeRequestHooks?: Array; /** * A list of custom hooks that must follow (err: unknown, req: Request) => Awaitable - * They are automatically added when a ChatMistralAI Object is created - * @default {[]} + * They are automatically added when a ChatMistralAI instance is created */ requestErrorHooks?: Array; /** * A list of custom hooks that must follow (res: Response, req: Request) => Awaitable - * They are automatically added when a ChatMistralAI Object is created - * @default {[]} + * They are automatically added when a ChatMistralAI instance is created */ responseHooks?: Array; /** @@ -121,6 +128,11 @@ export class MistralAI apiKey: string; + /** + * @deprecated use serverURL instead + */ + endpoint: string; + serverURL?: string; maxRetries?: number; @@ -145,7 +157,7 @@ export class MistralAI this.randomSeed = fields?.randomSeed ?? this.randomSeed; this.batchSize = fields?.batchSize ?? this.batchSize; this.streaming = fields?.streaming ?? this.streaming; - this.serverURL = fields?.serverURL; + this.serverURL = fields?.serverURL ?? this.serverURL; this.maxRetries = fields?.maxRetries; this.maxConcurrency = fields?.maxConcurrency; this.beforeRequestHooks = fields?.beforeRequestHooks ?? this.beforeRequestHooks; @@ -419,23 +431,23 @@ Either provide one via the "apiKey" field in the constructor, or set the "MISTRA this.requestErrorHooks, this.responseHooks ].some(hook => hook && hook.length > 0); - if(hasHooks && !this.httpClient) { + if (hasHooks && !this.httpClient) { this.httpClient = new MistralAIHTTPClient(); } - if(this.beforeRequestHooks) { + if (this.beforeRequestHooks) { for(const hook of this.beforeRequestHooks) { this.httpClient?.addHook("beforeRequest", hook); } } - if(this.requestErrorHooks) { + if (this.requestErrorHooks) { for(const hook of this.requestErrorHooks) { this.httpClient?.addHook("requestError", hook); } } - if(this.responseHooks) { + if (this.responseHooks) { for(const hook of this.responseHooks) { this.httpClient?.addHook("response", hook); } @@ -447,19 +459,19 @@ Either provide one via the "apiKey" field in the constructor, or set the "MISTRA removeAllHooksFromHttpClient() { try { - if(this.beforeRequestHooks) { + if (this.beforeRequestHooks) { for(const hook of this.beforeRequestHooks) { this.httpClient?.removeHook("beforeRequest", hook); } } - if(this.requestErrorHooks) { + if (this.requestErrorHooks) { for(const hook of this.requestErrorHooks) { this.httpClient?.removeHook("requestError", hook); } } - if(this.responseHooks) { + if (this.responseHooks) { for(const hook of this.responseHooks) { this.httpClient?.removeHook("response", hook); } From f81af226a651422be4f4231baa54a8e2c68ac8f7 Mon Sep 17 00:00:00 2001 From: BaNg-W Date: Sat, 9 Nov 2024 21:35:05 -0500 Subject: [PATCH 014/344] Integration Test debugging and fix implementations Co-authored-by: CarterMorris --- libs/langchain-mistralai/src/chat_models.ts | 81 +++++++++---------- libs/langchain-mistralai/src/embeddings.ts | 7 +- libs/langchain-mistralai/src/llms.ts | 6 +- .../src/tests/chat_models.int.test.ts | 65 ++++++--------- .../src/tests/llms.int.test.ts | 2 +- 5 files changed, 72 insertions(+), 89 deletions(-) diff --git a/libs/langchain-mistralai/src/chat_models.ts b/libs/langchain-mistralai/src/chat_models.ts index 3d80c670a94c..bb7aeb65cab8 100644 --- a/libs/langchain-mistralai/src/chat_models.ts +++ b/libs/langchain-mistralai/src/chat_models.ts @@ -176,17 +176,17 @@ export interface ChatMistralAIInput * A list of custom hooks that must follow (req: Request) => Awaitable * They are automatically added when a ChatMistralAI instance is created */ - beforeRequestHooks?: Array; + beforeRequestHooks?: BeforeRequestHook[]; /** * A list of custom hooks that must follow (err: unknown, req: Request) => Awaitable * They are automatically added when a ChatMistralAI instance is created */ - requestErrorHooks?: Array; + requestErrorHooks?: RequestErrorHook[]; /** * A list of custom hooks that must follow (res: Response, req: Request) => Awaitable * They are automatically added when a ChatMistralAI instance is created */ - responseHooks?: Array; + responseHooks?: ResponseHook[]; /** * Custom HTTP client to manage API requests * Allows users to add custom fetch implementations, hooks, as well as error and response processing. @@ -354,12 +354,12 @@ function mistralAIResponseToChatMessage( if (message === undefined) { throw new Error("No message found in response"); } - // MistralAI SDK does not include tool_calls in the non + // MistralAI SDK does not include toolCalls in the non // streaming return type, so we need to extract it like this // to satisfy typescript. let rawToolCalls: MistralAIToolCall[] = []; - if ("tool_calls" in message && Array.isArray(message.tool_calls)) { - rawToolCalls = message.tool_calls as MistralAIToolCall[]; + if ("toolCalls" in message && Array.isArray(message.toolCalls)) { + rawToolCalls = message.toolCalls; } switch (message.role) { case "assistant": { @@ -400,11 +400,11 @@ function _convertDeltaToMessageChunk( delta: { role?: string | undefined; content?: string | null | undefined; - tool_calls?: MistralAIToolCall[] | null | undefined; + toolCalls?: MistralAIToolCall[] | null | undefined; }, usage?: MistralAITokenUsage | null ) { - if (!delta.content && !delta.tool_calls) { + if (!delta.content && !delta.toolCalls) { if (usage) { return new AIMessageChunk({ content: "", @@ -422,8 +422,8 @@ function _convertDeltaToMessageChunk( // Our merge additional kwargs util function will throw unless there // is an index key in each tool object (as seen in OpenAI's) so we // need to insert it here. - const rawToolCallChunksWithIndex = delta.tool_calls?.length - ? delta.tool_calls?.map( + const rawToolCallChunksWithIndex = delta.toolCalls?.length + ? delta.toolCalls?.map( (toolCall, index): MistralAIToolCall & { index: number } => ({ ...toolCall, index, @@ -956,7 +956,7 @@ export class ChatMistralAI< */ invocationParams( options?: this["ParsedCallOptions"] - ): Omit { + ): Omit { const { response_format, tools, tool_choice } = options ?? {}; const mistralAITools: Array | undefined = tools?.length ? _convertToolToMistralTool(tools) @@ -988,7 +988,7 @@ export class ChatMistralAI< /** * Calls the MistralAI API with retry logic in case of failures. * @param {ChatRequest} input The input to send to the MistralAI API. - * @returns {Promise>} The response from the MistralAI API. + * @returns {Promise>} The response from the MistralAI API. */ async completionWithRetry( input: MistralAIChatCompletionStreamRequest, @@ -1282,7 +1282,6 @@ export class ChatMistralAI< RunOutput extends Record = Record >( outputSchema: - | StructuredOutputMethodParams | z.ZodType // eslint-disable-next-line @typescript-eslint/no-explicit-any | Record, @@ -1294,7 +1293,6 @@ export class ChatMistralAI< RunOutput extends Record = Record >( outputSchema: - | StructuredOutputMethodParams | z.ZodType // eslint-disable-next-line @typescript-eslint/no-explicit-any | Record, @@ -1306,7 +1304,6 @@ export class ChatMistralAI< RunOutput extends Record = Record >( outputSchema: - | StructuredOutputMethodParams | z.ZodType // eslint-disable-next-line @typescript-eslint/no-explicit-any | Record, @@ -1322,17 +1319,17 @@ export class ChatMistralAI< let name; let method; let includeRaw; - if (isStructuredOutputMethodParams(outputSchema)) { - schema = outputSchema.schema; - name = outputSchema.name; - method = outputSchema.method; - includeRaw = outputSchema.includeRaw; - } else { + // if (isStructuredOutputMethodParams(outputSchema)) { + // schema = outputSchema.schema; + // name = outputSchema.name; + // method = outputSchema.method; + // includeRaw = outputSchema.includeRaw; + // } else { schema = outputSchema; name = config?.name; method = config?.method; includeRaw = config?.includeRaw; - } + // } let llm: Runnable; let outputParser: BaseLLMOutputParser; @@ -1350,8 +1347,8 @@ export class ChatMistralAI< // Is function calling if (isZodSchema(schema)) { const asJsonSchema = zodToJsonSchema(schema); - llm = this.bind({ - tools: [ + llm = this.bindTools( + [ { type: "function" as const, function: { @@ -1361,8 +1358,9 @@ export class ChatMistralAI< }, }, ], - tool_choice: "any", - } as Partial); + { + tool_choice: "any" + } as Partial); outputParser = new JsonOutputKeyToolsParser({ returnSingle: true, keyName: functionName, @@ -1384,15 +1382,16 @@ export class ChatMistralAI< parameters: schema, }; } - llm = this.bind({ - tools: [ + llm = this.bindTools( + [ { type: "function" as const, function: openAIFunctionDefinition, }, ], - tool_choice: "any", - } as Partial); + { + tool_choice: "any" + } as Partial); outputParser = new JsonOutputKeyToolsParser({ returnSingle: true, keyName: functionName, @@ -1440,14 +1439,14 @@ function isZodSchema< return typeof (input as z.ZodType)?.parse === "function"; } -function isStructuredOutputMethodParams( - x: unknown - // eslint-disable-next-line @typescript-eslint/no-explicit-any -): x is StructuredOutputMethodParams> { - return ( - x !== undefined && - // eslint-disable-next-line @typescript-eslint/no-explicit-any - typeof (x as StructuredOutputMethodParams>).schema === - "object" - ); -} +// function isStructuredOutputMethodParams( +// x: unknown +// // eslint-disable-next-line @typescript-eslint/no-explicit-any +// ): x is StructuredOutputMethodParams> { +// return ( +// x !== undefined && +// // eslint-disable-next-line @typescript-eslint/no-explicit-any +// typeof (x as StructuredOutputMethodParams>).schema === +// "object" +// ); +// } diff --git a/libs/langchain-mistralai/src/embeddings.ts b/libs/langchain-mistralai/src/embeddings.ts index e4b3a1657eab..382e1fd65a19 100644 --- a/libs/langchain-mistralai/src/embeddings.ts +++ b/libs/langchain-mistralai/src/embeddings.ts @@ -60,17 +60,17 @@ export interface MistralAIEmbeddingsParams extends EmbeddingsParams { * A list of custom hooks that must follow (req: Request) => Awaitable * They are automatically added when a ChatMistralAI instance is created */ - beforeRequestHooks?: Array; + beforeRequestHooks?: BeforeRequestHook[]; /** * A list of custom hooks that must follow (err: unknown, req: Request) => Awaitable * They are automatically added when a ChatMistralAI instance is created */ - requestErrorHooks?: Array; + requestErrorHooks?: RequestErrorHook[]; /** * A list of custom hooks that must follow (res: Response, req: Request) => Awaitable * They are automatically added when a ChatMistralAI instance is created */ - responseHooks?: Array; + responseHooks?: ResponseHook[]; /** * Optional custom HTTP client to manage API requests * Allows users to add custom fetch implementations, hooks, as well as error and response processing. @@ -201,7 +201,6 @@ export class MistralAIEmbeddings return this.caller.call(async () => { const res = await client.embeddings.create( embeddingsRequest, - // Could add options here ); return res; }); diff --git a/libs/langchain-mistralai/src/llms.ts b/libs/langchain-mistralai/src/llms.ts index a0ff711f358c..ade20eab9a95 100644 --- a/libs/langchain-mistralai/src/llms.ts +++ b/libs/langchain-mistralai/src/llms.ts @@ -83,17 +83,17 @@ export interface MistralAIInput extends BaseLLMParams { * A list of custom hooks that must follow (req: Request) => Awaitable * They are automatically added when a ChatMistralAI instance is created */ - beforeRequestHooks?: Array; + beforeRequestHooks?: BeforeRequestHook[]; /** * A list of custom hooks that must follow (err: unknown, req: Request) => Awaitable * They are automatically added when a ChatMistralAI instance is created */ - requestErrorHooks?: Array; + requestErrorHooks?: RequestErrorHook[]; /** * A list of custom hooks that must follow (res: Response, req: Request) => Awaitable * They are automatically added when a ChatMistralAI instance is created */ - responseHooks?: Array; + responseHooks?: ResponseHook[]; /** * Optional custom HTTP client to manage API requests * Allows users to add custom fetch implementations, hooks, as well as error and response processing. diff --git a/libs/langchain-mistralai/src/tests/chat_models.int.test.ts b/libs/langchain-mistralai/src/tests/chat_models.int.test.ts index 39e88c34a0db..bcff1c08f91c 100644 --- a/libs/langchain-mistralai/src/tests/chat_models.int.test.ts +++ b/libs/langchain-mistralai/src/tests/chat_models.int.test.ts @@ -5,14 +5,13 @@ import { z } from "zod"; import { AIMessage, AIMessageChunk, - BaseMessage, HumanMessage, ToolMessage, } from "@langchain/core/messages"; import { zodToJsonSchema } from "zod-to-json-schema"; import { ChatMistralAI } from "../chat_models.js"; -test("Test ChatMistralAI can invoke", async () => { +test("Test ChatMistralAI can invoke hello", async () => { const model = new ChatMistralAI({ model: "mistral-tiny", }); @@ -80,19 +79,17 @@ test("Can call tools using structured tools", async () => { const chain = prompt.pipe(model); const response = await chain.invoke({}); - expect("tool_calls" in response.additional_kwargs).toBe(true); + expect("tool_calls" in response).toBe(true); // console.log(response.additional_kwargs.tool_calls?.[0]); - expect(response.additional_kwargs.tool_calls?.[0].function.name).toBe( + expect(response.tool_calls?.[0].name).toBe( "calculator" ); expect( - JSON.parse( - response.additional_kwargs.tool_calls?.[0].function.arguments ?? "{}" - ).calculator + response.tool_calls?.[0].args?.calculator ).toBeDefined(); }); -test("Can call tools", async () => { +test("Can call tools using raw tools", async () => { const tools = [ { type: "function", @@ -130,19 +127,11 @@ test("Can call tools", async () => { const response = await chain.invoke({}); // console.log(response); expect(response.tool_calls?.length).toEqual(1); - expect(response.tool_calls?.[0].args).toEqual( - JSON.parse( - response.additional_kwargs.tool_calls?.[0].function.arguments ?? "{}" - ) - ); - expect("tool_calls" in response.additional_kwargs).toBe(true); - expect(response.additional_kwargs.tool_calls?.[0].function.name).toBe( + expect(response.tool_calls?.[0].name).toBe( "calculator" ); expect( - JSON.parse( - response.additional_kwargs.tool_calls?.[0].function.arguments ?? "{}" - ).calculator + response.tool_calls?.[0].args?.calculator ).toBeDefined(); }); @@ -179,7 +168,7 @@ test("Can call .stream with tool calling", async () => { const chain = prompt.pipe(model); const response = await chain.stream({}); - let finalRes: BaseMessage | null = null; + let finalRes: AIMessageChunk | null = null; for await (const chunk of response) { // console.log(chunk); finalRes = chunk; @@ -188,15 +177,13 @@ test("Can call .stream with tool calling", async () => { throw new Error("No final response found"); } - expect("tool_calls" in finalRes.additional_kwargs).toBe(true); + expect("tool_calls" in finalRes).toBe(true); // console.log(finalRes.additional_kwargs.tool_calls?.[0]); - expect(finalRes.additional_kwargs.tool_calls?.[0].function.name).toBe( + expect(finalRes.tool_calls?.[0].name).toBe( "calculator" ); expect( - JSON.parse( - finalRes.additional_kwargs.tool_calls?.[0].function.arguments ?? "{}" - ).calculator + finalRes.tool_calls?.[0].args.calculator ).toBeDefined(); }); @@ -302,7 +289,7 @@ test("Can stream and concat responses for a complex tool", async () => { const chain = prompt.pipe(model); const response = await chain.stream({}); - let finalRes: BaseMessage[] = []; + let finalRes: AIMessageChunk[] = []; for await (const chunk of response) { // console.log(chunk); finalRes = finalRes.concat(chunk); @@ -311,11 +298,10 @@ test("Can stream and concat responses for a complex tool", async () => { throw new Error("No final response found"); } - expect(finalRes[0].additional_kwargs.tool_calls?.[0]).toBeDefined(); - const toolCall = finalRes[0].additional_kwargs.tool_calls?.[0]; - expect(toolCall?.function.name).toBe("person_traits"); - const args = JSON.parse(toolCall?.function.arguments ?? "{}"); - const { person } = args; + expect(finalRes[0].tool_calls?.[0]).toBeDefined(); + const toolCall = finalRes[0].tool_calls?.[0]; + expect(toolCall?.name).toBe("person_traits"); + const person = toolCall?.args?.person; expect(person).toBeDefined(); expect(person.name).toBeDefined(); expect(person.age).toBeDefined(); @@ -406,7 +392,7 @@ describe("withStructuredOutput", () => { ]); const chain = prompt.pipe(modelWithStructuredOutput); const result = await chain.invoke({}); - // console.log(result); + console.log(result); expect("operation" in result).toBe(true); expect("number1" in result).toBe(true); expect("number2" in result).toBe(true); @@ -812,7 +798,7 @@ describe("codestral-latest", () => { expect(fullMessage.toLowerCase()).toContain("world"); }); - test("Can call tools using structured tools codestral-latest", async () => { + test("Can call tools using codestral-latest structured tools", async () => { class CodeSandbox extends StructuredTool { name = "code_sandbox"; @@ -850,16 +836,15 @@ describe("codestral-latest", () => { "Write a function that takes in a single argument and logs it to the console. Ensure the code is in Python.", }); // console.log(response); - expect("tool_calls" in response.additional_kwargs).toBe(true); - // console.log(response.additional_kwargs.tool_calls?.[0]); - if (!response.additional_kwargs.tool_calls?.[0]) { + expect("tool_calls" in response).toBe(true); + // console.log(response.tool_calls?.[0]); + if (!response.tool_calls?.[0]) { throw new Error("No tool call found"); } - const sandboxTool = response.additional_kwargs.tool_calls[0]; - expect(sandboxTool.function.name).toBe("code_sandbox"); - const parsedArgs = JSON.parse(sandboxTool.function.arguments); - expect(parsedArgs.code).toBeDefined(); - // console.log(parsedArgs.code); + const sandboxTool = response.tool_calls[0]; + expect(sandboxTool.name).toBe("code_sandbox"); + expect(sandboxTool.args?.code).toBeDefined(); + // console.log(sandboxTool.args?.code); }); }); diff --git a/libs/langchain-mistralai/src/tests/llms.int.test.ts b/libs/langchain-mistralai/src/tests/llms.int.test.ts index 6fc263a0eb00..bc51e1404083 100644 --- a/libs/langchain-mistralai/src/tests/llms.int.test.ts +++ b/libs/langchain-mistralai/src/tests/llms.int.test.ts @@ -7,7 +7,7 @@ import { MistralAI } from "../llms.js"; // Save the original value of the 'LANGCHAIN_CALLBACKS_BACKGROUND' environment variable const originalBackground = process.env.LANGCHAIN_CALLBACKS_BACKGROUND; -test("Test MistralAI", async () => { +test("Test MistralAI default", async () => { const model = new MistralAI({ maxTokens: 5, model: "codestral-latest", From 58b839edd34f105515e09cb8cbebb7787ce39d73 Mon Sep 17 00:00:00 2001 From: CarterMorris Date: Sun, 10 Nov 2024 16:58:44 -0500 Subject: [PATCH 015/344] Reverted bindTools to bind in withStructuredOutput --- libs/langchain-mistralai/src/chat_models.ts | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/libs/langchain-mistralai/src/chat_models.ts b/libs/langchain-mistralai/src/chat_models.ts index bb7aeb65cab8..2d78b74334e1 100644 --- a/libs/langchain-mistralai/src/chat_models.ts +++ b/libs/langchain-mistralai/src/chat_models.ts @@ -1347,8 +1347,8 @@ export class ChatMistralAI< // Is function calling if (isZodSchema(schema)) { const asJsonSchema = zodToJsonSchema(schema); - llm = this.bindTools( - [ + llm = this.bind({ + tools: [ { type: "function" as const, function: { @@ -1358,9 +1358,8 @@ export class ChatMistralAI< }, }, ], - { - tool_choice: "any" - } as Partial); + tool_choice: "any", + } as Partial); outputParser = new JsonOutputKeyToolsParser({ returnSingle: true, keyName: functionName, @@ -1382,15 +1381,14 @@ export class ChatMistralAI< parameters: schema, }; } - llm = this.bindTools( - [ + llm = this.bind({ + tools: [ { type: "function" as const, function: openAIFunctionDefinition, }, ], - { - tool_choice: "any" + tool_choice: "any", } as Partial); outputParser = new JsonOutputKeyToolsParser({ returnSingle: true, From bfceabd07bce808c870473029e759b4ce3026ee8 Mon Sep 17 00:00:00 2001 From: Jacob Lee Date: Tue, 13 Aug 2024 00:23:31 -0700 Subject: [PATCH 016/344] Improve docstring format for streamEvents (#6513) --- langchain-core/src/runnables/base.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/langchain-core/src/runnables/base.ts b/langchain-core/src/runnables/base.ts index 4cd865377be9..d79a19a17718 100644 --- a/langchain-core/src/runnables/base.ts +++ b/langchain-core/src/runnables/base.ts @@ -760,6 +760,7 @@ export abstract class Runnable< * * **ATTENTION** This reference table is for the V2 version of the schema. * + * ```md * +----------------------+------------------+---------------------------------+-----------------------------------------------+-------------------------------------------------+ * | event | name | chunk | input | output | * +======================+==================+=================================+===============================================+=================================================+ @@ -793,6 +794,7 @@ export abstract class Runnable< * +----------------------+------------------+---------------------------------+-----------------------------------------------+-------------------------------------------------+ * | on_prompt_end | [template_name] | | {"question": "hello"} | ChatPromptValue(messages: [SystemMessage, ...]) | * +----------------------+------------------+---------------------------------+-----------------------------------------------+-------------------------------------------------+ + * ``` * * The "on_chain_*" events are the default for Runnables that don't fit one of the above categories. * @@ -802,6 +804,7 @@ export abstract class Runnable< * * A custom event has following format: * + * ```md * +-----------+------+-----------------------------------------------------------------------------------------------------------+ * | Attribute | Type | Description | * +===========+======+===========================================================================================================+ @@ -809,9 +812,10 @@ export abstract class Runnable< * +-----------+------+-----------------------------------------------------------------------------------------------------------+ * | data | Any | The data associated with the event. This can be anything, though we suggest making it JSON serializable. | * +-----------+------+-----------------------------------------------------------------------------------------------------------+ + * ``` * * Here's an example: - * @example + * * ```ts * import { RunnableLambda } from "@langchain/core/runnables"; * import { dispatchCustomEvent } from "@langchain/core/callbacks/dispatch"; From 94a0dd2f5be6ac27ffed8f17fca943789ca4d79d Mon Sep 17 00:00:00 2001 From: Jacob Lee Date: Tue, 13 Aug 2024 01:47:48 -0700 Subject: [PATCH 017/344] Fix self-query retriever tracing (#6515) --- .../src/retrievers/self_query/functional.ts | 2 +- langchain/src/retrievers/self_query/index.ts | 17 ++++++++++------- .../tests/memory_self_query.int.test.ts | 16 ++++++++-------- 3 files changed, 19 insertions(+), 16 deletions(-) diff --git a/langchain/src/retrievers/self_query/functional.ts b/langchain/src/retrievers/self_query/functional.ts index c3b7b9aebfaa..b38b65569e76 100644 --- a/langchain/src/retrievers/self_query/functional.ts +++ b/langchain/src/retrievers/self_query/functional.ts @@ -1,4 +1,4 @@ export { - FunctionFilter, + type FunctionFilter, FunctionalTranslator, } from "@langchain/core/structured_query"; diff --git a/langchain/src/retrievers/self_query/index.ts b/langchain/src/retrievers/self_query/index.ts index 6b348104a4b8..3758f8267dcd 100644 --- a/langchain/src/retrievers/self_query/index.ts +++ b/langchain/src/retrievers/self_query/index.ts @@ -101,7 +101,10 @@ export class SelfQueryRetriever ): Promise>[]> { const generatedStructuredQuery = await this.queryConstructor.invoke( { query }, - runManager?.getChild("query_constructor") + { + callbacks: runManager?.getChild("query_constructor"), + runName: "query_constructor", + } ); const nextArg = this.structuredQueryTranslator.visitStructuredQuery( @@ -122,12 +125,12 @@ export class SelfQueryRetriever myQuery = generatedQuery; } - return this.vectorStore.similaritySearch( - myQuery, - this.searchParams?.k, - filter, - runManager?.getChild("vectorstore") - ); + return this.vectorStore + .asRetriever({ + k: this.searchParams?.k, + filter, + }) + .invoke(myQuery, { callbacks: runManager?.getChild("retriever") }); } /** diff --git a/langchain/src/retrievers/self_query/tests/memory_self_query.int.test.ts b/langchain/src/retrievers/self_query/tests/memory_self_query.int.test.ts index 6321155c1655..695349dbc3c3 100644 --- a/langchain/src/retrievers/self_query/tests/memory_self_query.int.test.ts +++ b/langchain/src/retrievers/self_query/tests/memory_self_query.int.test.ts @@ -1,6 +1,6 @@ import { test } from "@jest/globals"; import { Document } from "@langchain/core/documents"; -import { OpenAIEmbeddings, OpenAI } from "@langchain/openai"; +import { OpenAIEmbeddings, ChatOpenAI } from "@langchain/openai"; import { AttributeInfo } from "../../../chains/query_constructor/index.js"; import { SelfQueryRetriever } from "../index.js"; import { FunctionalTranslator } from "../functional.js"; @@ -73,8 +73,8 @@ test("Memory Vector Store Self Query Retriever Test", async () => { ]; const embeddings = new OpenAIEmbeddings(); - const llm = new OpenAI({ - modelName: "gpt-3.5-turbo", + const llm = new ChatOpenAI({ + model: "gpt-4o-mini", }); const documentContents = "Brief summary of a movie"; const vectorStore = await MemoryVectorStore.fromDocuments(docs, embeddings); @@ -203,8 +203,8 @@ test("Memory Vector Store Self Query Retriever Test With Default Filter Or Merge ]; const embeddings = new OpenAIEmbeddings(); - const llm = new OpenAI({ - modelName: "gpt-3.5-turbo", + const llm = new ChatOpenAI({ + model: "gpt-4o-mini", }); const documentContents = "Brief summary of a movie"; const vectorStore = await MemoryVectorStore.fromDocuments(docs, embeddings); @@ -344,8 +344,8 @@ test("Memory Vector Store Self Query Retriever Test With Default Filter And Merg ]; const embeddings = new OpenAIEmbeddings(); - const llm = new OpenAI({ - modelName: "gpt-3.5-turbo", + const llm = new ChatOpenAI({ + model: "gpt-4o-mini", }); const documentContents = "Brief summary of a movie"; const vectorStore = await MemoryVectorStore.fromDocuments(docs, embeddings); @@ -384,5 +384,5 @@ test("Memory Vector Store Self Query Retriever Test With Default Filter And Merg "Awawawa au au au wawawawa hello?" ); // console.log(query4); - expect(query4.length).toEqual(0); // this one should return documents since default filter takes over + expect(query4.length).toEqual(7); // this one should return documents since default filter takes over }); From 7f29dc13fec791f87cdd0ea683bb16da1c894e09 Mon Sep 17 00:00:00 2001 From: Jacob Lee Date: Tue, 13 Aug 2024 01:56:57 -0700 Subject: [PATCH 018/344] Skip API ref preview deploy on PR (#6516) --- .github/workflows/deploy-api-refs-preview.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/deploy-api-refs-preview.yml b/.github/workflows/deploy-api-refs-preview.yml index db2c512d18fa..65904ae772a0 100644 --- a/.github/workflows/deploy-api-refs-preview.yml +++ b/.github/workflows/deploy-api-refs-preview.yml @@ -2,7 +2,8 @@ name: Deploy API Refs Preview on: workflow_dispatch: # Allows triggering the workflow manually in GitHub UI - pull_request: + # Avoid deploying on PR due to rate limits + # pull_request: # If another push to the same PR or branch happens while this workflow is still running, # cancel the earlier run in favor of the next run. From 38cf36a94fd92071ce5442accea304c0b45ba564 Mon Sep 17 00:00:00 2001 From: Jacob Lee Date: Tue, 13 Aug 2024 04:42:21 -0700 Subject: [PATCH 019/344] Release 0.2.16 (#6517) --- langchain/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/langchain/package.json b/langchain/package.json index 75af947d0767..bbf93f6eab95 100644 --- a/langchain/package.json +++ b/langchain/package.json @@ -1,6 +1,6 @@ { "name": "langchain", - "version": "0.2.15", + "version": "0.2.16", "description": "Typescript bindings for langchain", "type": "module", "engines": { From 2772d77029a1cf18388a59468a958095f36b1f9d Mon Sep 17 00:00:00 2001 From: Jacob Lee Date: Tue, 13 Aug 2024 15:30:09 -0700 Subject: [PATCH 020/344] docs[patch]: Adds vector store and tool docstrings (#6521) --- .../tools/duckduckgo_search.ipynb | 2 +- .../integrations/tools/tavily_search.ipynb | 2 +- .../integrations/vectorstores/memory.ipynb | 2 +- langchain/src/vectorstores/memory.ts | 111 ++++++++++++- .../src/tools/duckduckgo_search.ts | 65 +++++++- .../src/tools/tavily_search.ts | 65 +++++++- .../src/tools/wikipedia_query_run.ts | 64 +++++++- .../src/vectorstores/chroma.ts | 38 ++--- .../src/vectorstores/pgvector.ts | 150 +++++++++++++++++- .../src/vectorstores/supabase.ts | 133 +++++++++++++++- libs/langchain-exa/src/tools.ts | 70 +++++++- libs/langchain-pinecone/src/vectorstores.ts | 133 +++++++++++++++- 12 files changed, 788 insertions(+), 47 deletions(-) diff --git a/docs/core_docs/docs/integrations/tools/duckduckgo_search.ipynb b/docs/core_docs/docs/integrations/tools/duckduckgo_search.ipynb index e0053c4cd9b1..1ff5b72ffdea 100644 --- a/docs/core_docs/docs/integrations/tools/duckduckgo_search.ipynb +++ b/docs/core_docs/docs/integrations/tools/duckduckgo_search.ipynb @@ -30,7 +30,7 @@ "### Integration details\n", "\n", "| Class | Package | [PY support](https://python.langchain.com/docs/integrations/tools/ddg/) | Package latest |\n", - "| :--- | :--- | :---: | :---: | :---: |\n", + "| :--- | :--- | :---: | :---: |\n", "| [DuckDuckGoSearch](https://api.js.langchain.com/classes/langchain_community_tools_duckduckgo_search.DuckDuckGoSearch.html) | [`@langchain/community`](https://www.npmjs.com/package/@langchain/community) | ✅ | ![NPM - Version](https://img.shields.io/npm/v/@langchain/community?style=flat-square&label=%20&) |\n", "\n", "## Setup\n", diff --git a/docs/core_docs/docs/integrations/tools/tavily_search.ipynb b/docs/core_docs/docs/integrations/tools/tavily_search.ipynb index ec6b12c0a0dd..ff63e76ba54c 100644 --- a/docs/core_docs/docs/integrations/tools/tavily_search.ipynb +++ b/docs/core_docs/docs/integrations/tools/tavily_search.ipynb @@ -30,7 +30,7 @@ "### Integration details\n", "\n", "| Class | Package | [PY support](https://python.langchain.com/v0.2/docs/integrations/tools/tavily_search/) | Package latest |\n", - "| :--- | :--- | :---: | :---: | :---: |\n", + "| :--- | :--- | :---: | :---: |\n", "| [TavilySearchResults](https://api.js.langchain.com/classes/langchain_community_tools_tavily_search.TavilySearchResults.html) | [`@langchain/community`](https://www.npmjs.com/package/@langchain/community) | ✅ | ![NPM - Version](https://img.shields.io/npm/v/@langchain/community?style=flat-square&label=%20&) |\n", "\n", "## Setup\n", diff --git a/docs/core_docs/docs/integrations/vectorstores/memory.ipynb b/docs/core_docs/docs/integrations/vectorstores/memory.ipynb index 8943d85715a9..923ac8d2a746 100644 --- a/docs/core_docs/docs/integrations/vectorstores/memory.ipynb +++ b/docs/core_docs/docs/integrations/vectorstores/memory.ipynb @@ -19,7 +19,7 @@ "id": "ef1f0986", "metadata": {}, "source": [ - "# `MemoryVectorStore`\n", + "# MemoryVectorStore\n", "\n", "LangChain offers is an in-memory, ephemeral vectorstore that stores embeddings in-memory and does an exact, linear search for the most similar embeddings. The default similarity metric is cosine similarity, but can be changed to any of the similarity metrics supported by [ml-distance](https://mljs.github.io/distance/modules/similarity.html).\n", "\n", diff --git a/langchain/src/vectorstores/memory.ts b/langchain/src/vectorstores/memory.ts index 50c3fdc4526a..fc77840a77ab 100644 --- a/langchain/src/vectorstores/memory.ts +++ b/langchain/src/vectorstores/memory.ts @@ -29,9 +29,114 @@ export interface MemoryVectorStoreArgs { } /** - * Class that extends `VectorStore` to store vectors in memory. Provides - * methods for adding documents, performing similarity searches, and - * creating instances from texts, documents, or an existing index. + * In-memory, ephemeral vector store. + * + * Setup: + * Install `langchain`: + * + * ```bash + * npm install langchain + * ``` + * + * ## [Constructor args](https://api.js.langchain.com/classes/langchain.vectorstores_memory.MemoryVectorStore.html#constructor) + * + *
+ * Instantiate + * + * ```typescript + * import { MemoryVectorStore } from 'langchain/vectorstores/memory'; + * // Or other embeddings + * import { OpenAIEmbeddings } from '@langchain/openai'; + * + * const embeddings = new OpenAIEmbeddings({ + * model: "text-embedding-3-small", + * }); + * + * const vectorStore = new MemoryVectorStore(embeddings); + * ``` + *
+ * + *
+ * + *
+ * Add documents + * + * ```typescript + * import type { Document } from '@langchain/core/documents'; + * + * const document1 = { pageContent: "foo", metadata: { baz: "bar" } }; + * const document2 = { pageContent: "thud", metadata: { bar: "baz" } }; + * const document3 = { pageContent: "i will be deleted :(", metadata: {} }; + * + * const documents: Document[] = [document1, document2, document3]; + * + * await vectorStore.addDocuments(documents); + * ``` + *
+ * + *
+ * + *
+ * Similarity search + * + * ```typescript + * const results = await vectorStore.similaritySearch("thud", 1); + * for (const doc of results) { + * console.log(`* ${doc.pageContent} [${JSON.stringify(doc.metadata, null)}]`); + * } + * // Output: * thud [{"baz":"bar"}] + * ``` + *
+ * + *
+ * + * + *
+ * Similarity search with filter + * + * ```typescript + * const resultsWithFilter = await vectorStore.similaritySearch("thud", 1, { baz: "bar" }); + * + * for (const doc of resultsWithFilter) { + * console.log(`* ${doc.pageContent} [${JSON.stringify(doc.metadata, null)}]`); + * } + * // Output: * foo [{"baz":"bar"}] + * ``` + *
+ * + *
+ * + * + *
+ * Similarity search with score + * + * ```typescript + * const resultsWithScore = await vectorStore.similaritySearchWithScore("qux", 1); + * for (const [doc, score] of resultsWithScore) { + * console.log(`* [SIM=${score.toFixed(6)}] ${doc.pageContent} [${JSON.stringify(doc.metadata, null)}]`); + * } + * // Output: * [SIM=0.000000] qux [{"bar":"baz","baz":"bar"}] + * ``` + *
+ * + *
+ * + *
+ * As a retriever + * + * ```typescript + * const retriever = vectorStore.asRetriever({ + * searchType: "mmr", // Leave blank for standard similarity search + * k: 1, + * }); + * const resultAsRetriever = await retriever.invoke("thud"); + * console.log(resultAsRetriever); + * + * // Output: [Document({ metadata: { "baz":"bar" }, pageContent: "thud" })] + * ``` + *
+ * + *
*/ export class MemoryVectorStore extends VectorStore { declare FilterType: (doc: Document) => boolean; diff --git a/libs/langchain-community/src/tools/duckduckgo_search.ts b/libs/langchain-community/src/tools/duckduckgo_search.ts index 70c440253a1c..7bfd04307a89 100644 --- a/libs/langchain-community/src/tools/duckduckgo_search.ts +++ b/libs/langchain-community/src/tools/duckduckgo_search.ts @@ -24,8 +24,69 @@ export interface DuckDuckGoSearchParameters extends ToolParams { const DEFAULT_MAX_RESULTS = 10; /** - * Class for interacting with the DuckDuckGo search engine - * It extends the base Tool class to perform retrieval. + * DuckDuckGo tool integration. + * + * Setup: + * Install `@langchain/community` and `duck-duck-scrape`. + * + * ```bash + * npm install @langchain/community duck-duck-scrape + * ``` + * + * ## [Constructor args](https://api.js.langchain.com/classes/_langchain_community.tools_duckduckgo_search.DuckDuckGoSearch.html#constructor) + * + *
+ * Instantiate + * + * ```typescript + * import { DuckDuckGoSearch } from "@langchain/community/tools/duckduckgo_search"; + * + * const tool = new DuckDuckGoSearch({ maxResults: 1 }); + * ``` + *
+ * + *
+ * + *
+ * + * Invocation + * + * ```typescript + * await tool.invoke("what is the current weather in sf?"); + * + * // output: [{"title":"San Francisco, CA Current Weather | AccuWeather","link":"https://www.accuweather.com/en/us/san-francisco/94103/current-weather/347629","snippet":"Current weather in San Francisco, CA. Check current conditions in San Francisco, CA with radar, hourly, and more."}] + * ``` + *
+ * + *
+ * + *
+ * + * Invocation with tool call + * + * ```typescript + * // This is usually generated by a model, but we'll create a tool call directly for demo purposes. + * const modelGeneratedToolCall = { + * args: { + * input: "what is the current weather in sf?", + * }, + * id: "tool_call_id", + * name: tool.name, + * type: "tool_call", + * }; + * await tool.invoke(modelGeneratedToolCall); + * ``` + * + * ```text + * ToolMessage { + * "content": "[{\"title\":\"San Francisco, CA Weather Conditions | Weather Underground\",\"link\":\"https://www.wunderground.com/weather/us/ca/san-francisco\",\"snippet\":\"San Francisco Weather Forecasts. Weather Underground provides local & long-range weather forecasts, weatherreports, maps & tropical weather conditions for the San Francisco area.\"}]", + * "name": "duckduckgo-search", + * "additional_kwargs": {}, + * "response_metadata": {}, + * "tool_call_id": "tool_call_id" + * } + * ``` + *
*/ export class DuckDuckGoSearch extends Tool { private searchOptions?: SearchOptions; diff --git a/libs/langchain-community/src/tools/tavily_search.ts b/libs/langchain-community/src/tools/tavily_search.ts index a68f97d5e588..b83a042f7888 100644 --- a/libs/langchain-community/src/tools/tavily_search.ts +++ b/libs/langchain-community/src/tools/tavily_search.ts @@ -12,7 +12,70 @@ export type TavilySearchAPIRetrieverFields = ToolParams & { }; /** - * Tool for the Tavily search API. + * Tavily search API tool integration. + * + * Setup: + * Install `@langchain/community`. You'll also need an API key set as `TAVILY_API_KEY`. + * + * ```bash + * npm install @langchain/community + * ``` + * + * ## [Constructor args](https://api.js.langchain.com/classes/_langchain_community.tools_tavily_search.TavilySearchResults.html#constructor) + * + *
+ * Instantiate + * + * ```typescript + * import { TavilySearchResults } from "@langchain/community/tools/tavily_search"; + * + * const tool = new TavilySearchResults({ + * maxResults: 2, + * // ... + * }); + * ``` + *
+ * + *
+ * + *
+ * + * Invocation + * + * ```typescript + * await tool.invoke("what is the current weather in sf?"); + * ``` + *
+ * + *
+ * + *
+ * + * Invocation with tool call + * + * ```typescript + * // This is usually generated by a model, but we'll create a tool call directly for demo purposes. + * const modelGeneratedToolCall = { + * args: { + * input: "what is the current weather in sf?", + * }, + * id: "tool_call_id", + * name: tool.name, + * type: "tool_call", + * }; + * await tool.invoke(modelGeneratedToolCall); + * ``` + * + * ```text + * ToolMessage { + * "content": "...", + * "name": "tavily_search_results_json", + * "additional_kwargs": {}, + * "response_metadata": {}, + * "tool_call_id": "tool_call_id" + * } + * ``` + *
*/ export class TavilySearchResults extends Tool { static lc_name(): string { diff --git a/libs/langchain-community/src/tools/wikipedia_query_run.ts b/libs/langchain-community/src/tools/wikipedia_query_run.ts index d0166e5c58d0..2c2f6b066761 100644 --- a/libs/langchain-community/src/tools/wikipedia_query_run.ts +++ b/libs/langchain-community/src/tools/wikipedia_query_run.ts @@ -53,16 +53,70 @@ interface PageResult { } /** - * Class for interacting with and fetching data from the Wikipedia API. It - * extends the Tool class. - * @example + * Wikipedia query tool integration. + * + * Setup: + * Install `@langchain/community`. You'll also need an API key. + * + * ```bash + * npm install @langchain/community + * ``` + * + * ## [Constructor args](https://api.js.langchain.com/classes/_langchain_community.tools_wikipedia_query_run.WikipediaQueryRun.html#constructor) + * + *
+ * Instantiate + * * ```typescript - * const wikipediaQuery = new WikipediaQueryRun({ + * import { WikipediaQueryRun } from "@langchain/community/tools/wikipedia_query_run"; + * + * const tool = new WikipediaQueryRun({ * topKResults: 3, * maxDocContentLength: 4000, * }); - * const result = await wikipediaQuery.call("Langchain"); * ``` + *
+ * + *
+ * + *
+ * + * Invocation + * + * ```typescript + * await tool.invoke("what is the current weather in sf?"); + * ``` + *
+ * + *
+ * + *
+ * + * Invocation with tool call + * + * ```typescript + * // This is usually generated by a model, but we'll create a tool call directly for demo purposes. + * const modelGeneratedToolCall = { + * args: { + * input: "what is the current weather in sf?", + * }, + * id: "tool_call_id", + * name: tool.name, + * type: "tool_call", + * }; + * await tool.invoke(modelGeneratedToolCall); + * ``` + * + * ```text + * ToolMessage { + * "content": "...", + * "name": "wikipedia-api", + * "additional_kwargs": {}, + * "response_metadata": {}, + * "tool_call_id": "tool_call_id" + * } + * ``` + *
*/ export class WikipediaQueryRun extends Tool { static lc_name() { diff --git a/libs/langchain-community/src/vectorstores/chroma.ts b/libs/langchain-community/src/vectorstores/chroma.ts index 7cec56f83429..81731fbef087 100644 --- a/libs/langchain-community/src/vectorstores/chroma.ts +++ b/libs/langchain-community/src/vectorstores/chroma.ts @@ -49,24 +49,12 @@ export interface ChromaDeleteParams { * Chroma vector store integration. * * Setup: - * Install `@langchain/community` and `chromadb`, then clone the official `ChromaDB` repository. + * Install `@langchain/community` and `chromadb`. * * ```bash * npm install @langchain/community chromadb * ``` * - * ```bash - * git clone https://github.com/chroma-core/chroma.git - * ``` - * - * Next, navigate into the `chroma` directory and start the docker container: - * - * ```bash - * cd ./chroma - * - * docker compose up - * ``` - * * ## [Constructor args](https://api.js.langchain.com/classes/langchain_community_vectorstores_chroma.Chroma.html#constructor) * *
@@ -74,10 +62,15 @@ export interface ChromaDeleteParams { * * ```typescript * import { Chroma } from '@langchain/community/vectorstores/chroma'; + * // Or other embeddings * import { OpenAIEmbeddings } from '@langchain/openai'; * + * const embeddings = new OpenAIEmbeddings({ + * model: "text-embedding-3-small", + * }) + * * const vectorStore = new Chroma( - * new OpenAIEmbeddings(), + * embeddings, * { * collectionName: "foo", * url: "http://localhost:8000", // URL of the Chroma server @@ -92,13 +85,13 @@ export interface ChromaDeleteParams { * Add documents * * ```typescript - * import { Document } from '@langchain/core/documents'; + * import type { Document } from '@langchain/core/documents'; * - * const document1 = new Document({ pageContent: "foo", metadata: { baz: "bar" } }); - * const document2 = new Document({ pageContent: "thud", metadata: { bar: "baz" } }); - * const document3 = new Document({ pageContent: "i will be deleted :(" }); + * const document1 = { pageContent: "foo", metadata: { baz: "bar" } }; + * const document2 = { pageContent: "thud", metadata: { bar: "baz" } }; + * const document3 = { pageContent: "i will be deleted :(", metadata: {} }; * - * const documents = [document1, document2, document3]; + * const documents: Document[] = [document1, document2, document3]; * const ids = ["1", "2", "3"]; * await vectorStore.addDocuments(documents, { ids }); * ``` @@ -166,7 +159,7 @@ export interface ChromaDeleteParams { * * ```typescript * const retriever = vectorStore.asRetriever({ - * searchType: "mmr", + * searchType: "mmr", // Leave blank for standard similarity search * k: 1, * }); * const resultAsRetriever = await retriever.invoke("thud"); @@ -473,10 +466,7 @@ export class Chroma extends VectorStore { return instance; } - /** - * Imports the `ChromaClient` from the `chromadb` module. - * @returns A promise that resolves with an object containing the `ChromaClient` constructor. - */ + /** @ignore */ static async imports(): Promise<{ ChromaClient: typeof ChromaClientT; }> { diff --git a/libs/langchain-community/src/vectorstores/pgvector.ts b/libs/langchain-community/src/vectorstores/pgvector.ts index 662ce96d2889..ded20864b9f4 100644 --- a/libs/langchain-community/src/vectorstores/pgvector.ts +++ b/libs/langchain-community/src/vectorstores/pgvector.ts @@ -41,10 +41,152 @@ export interface PGVectorStoreArgs { } /** - * Class that provides an interface to a Postgres vector database. It - * extends the `VectorStore` base class and implements methods for adding - * documents and vectors, performing similarity searches, and ensuring the - * existence of a table in the database. + * PGVector vector store integration. + * + * Setup: + * Install `@langchain/community` and `pg`. + * + * If you wish to generate ids, you should also install the `uuid` package. + * + * ```bash + * npm install @langchain/community pg uuid + * ``` + * + * ## [Constructor args](https://api.js.langchain.com/classes/_langchain_community.vectorstores_pgvector.PGVectorStore.html#constructor) + * + *
+ * Instantiate + * + * ```typescript + * import { + * PGVectorStore, + * DistanceStrategy, + * } from "@langchain/community/vectorstores/pgvector"; + * + * // Or other embeddings + * import { OpenAIEmbeddings } from "@langchain/openai"; + * import { PoolConfig } from "pg"; + * + * const embeddings = new OpenAIEmbeddings({ + * model: "text-embedding-3-small", + * }); + * + * // Sample config + * const config = { + * postgresConnectionOptions: { + * type: "postgres", + * host: "127.0.0.1", + * port: 5433, + * user: "myuser", + * password: "ChangeMe", + * database: "api", + * } as PoolConfig, + * tableName: "testlangchainjs", + * columns: { + * idColumnName: "id", + * vectorColumnName: "vector", + * contentColumnName: "content", + * metadataColumnName: "metadata", + * }, + * // supported distance strategies: cosine (default), innerProduct, or euclidean + * distanceStrategy: "cosine" as DistanceStrategy, + * }; + * + * const vectorStore = await PGVectorStore.initialize(embeddings, config); + * ``` + *
+ * + *
+ * + *
+ * Add documents + * + * ```typescript + * import type { Document } from '@langchain/core/documents'; + * + * const document1 = { pageContent: "foo", metadata: { baz: "bar" } }; + * const document2 = { pageContent: "thud", metadata: { bar: "baz" } }; + * const document3 = { pageContent: "i will be deleted :(", metadata: {} }; + * + * const documents: Document[] = [document1, document2, document3]; + * const ids = ["1", "2", "3"]; + * await vectorStore.addDocuments(documents, { ids }); + * ``` + *
+ * + *
+ * + *
+ * Delete documents + * + * ```typescript + * await vectorStore.delete({ ids: ["3"] }); + * ``` + *
+ * + *
+ * + *
+ * Similarity search + * + * ```typescript + * const results = await vectorStore.similaritySearch("thud", 1); + * for (const doc of results) { + * console.log(`* ${doc.pageContent} [${JSON.stringify(doc.metadata, null)}]`); + * } + * // Output: * thud [{"baz":"bar"}] + * ``` + *
+ * + *
+ * + * + *
+ * Similarity search with filter + * + * ```typescript + * const resultsWithFilter = await vectorStore.similaritySearch("thud", 1, { baz: "bar" }); + * + * for (const doc of resultsWithFilter) { + * console.log(`* ${doc.pageContent} [${JSON.stringify(doc.metadata, null)}]`); + * } + * // Output: * foo [{"baz":"bar"}] + * ``` + *
+ * + *
+ * + * + *
+ * Similarity search with score + * + * ```typescript + * const resultsWithScore = await vectorStore.similaritySearchWithScore("qux", 1); + * for (const [doc, score] of resultsWithScore) { + * console.log(`* [SIM=${score.toFixed(6)}] ${doc.pageContent} [${JSON.stringify(doc.metadata, null)}]`); + * } + * // Output: * [SIM=0.000000] qux [{"bar":"baz","baz":"bar"}] + * ``` + *
+ * + *
+ * + *
+ * As a retriever + * + * ```typescript + * const retriever = vectorStore.asRetriever({ + * searchType: "mmr", // Leave blank for standard similarity search + * k: 1, + * }); + * const resultAsRetriever = await retriever.invoke("thud"); + * console.log(resultAsRetriever); + * + * // Output: [Document({ metadata: { "baz":"bar" }, pageContent: "thud" })] + * ``` + *
+ * + *
*/ export class PGVectorStore extends VectorStore { declare FilterType: Metadata; diff --git a/libs/langchain-community/src/vectorstores/supabase.ts b/libs/langchain-community/src/vectorstores/supabase.ts index e12eb260933d..e8875c3aab9d 100644 --- a/libs/langchain-community/src/vectorstores/supabase.ts +++ b/libs/langchain-community/src/vectorstores/supabase.ts @@ -46,8 +46,137 @@ export interface SupabaseLibArgs { } /** - * Class for interacting with a Supabase database to store and manage - * vectors. + * Supabase vector store integration. + * + * Setup: + * Install `@langchain/community` and `@supabase/supabase-js`. + * + * ```bash + * npm install @langchain/community @supabase/supabase-js + * ``` + * + * See https://js.langchain.com/v0.2/docs/integrations/vectorstores/supabase for + * instructions on how to set up your Supabase instance. + * + * ## [Constructor args](https://api.js.langchain.com/classes/_langchain_community.vectorstores_supabase.SupabaseVectorStore.html#constructor) + * + *
+ * Instantiate + * + * ```typescript + * import { SupabaseVectorStore } from "@langchain/community/vectorstores/supabase"; + * import { OpenAIEmbeddings } from "@langchain/openai"; + * + * import { createClient } from "@supabase/supabase-js"; + * + * const embeddings = new OpenAIEmbeddings({ + * model: "text-embedding-3-small", + * }); + * + * const supabaseClient = createClient( + * process.env.SUPABASE_URL, + * process.env.SUPABASE_PRIVATE_KEY + * ); + * + * const vectorStore = new SupabaseVectorStore(embeddings, { + * client: supabaseClient, + * tableName: "documents", + * queryName: "match_documents", + * }); + * ``` + *
+ * + *
+ * + *
+ * Add documents + * + * ```typescript + * import type { Document } from '@langchain/core/documents'; + * + * const document1 = { pageContent: "foo", metadata: { baz: "bar" } }; + * const document2 = { pageContent: "thud", metadata: { bar: "baz" } }; + * const document3 = { pageContent: "i will be deleted :(", metadata: {} }; + * + * const documents: Document[] = [document1, document2, document3]; + * const ids = ["1", "2", "3"]; + * await vectorStore.addDocuments(documents, { ids }); + * ``` + *
+ * + *
+ * + *
+ * Delete documents + * + * ```typescript + * await vectorStore.delete({ ids: ["3"] }); + * ``` + *
+ * + *
+ * + *
+ * Similarity search + * + * ```typescript + * const results = await vectorStore.similaritySearch("thud", 1); + * for (const doc of results) { + * console.log(`* ${doc.pageContent} [${JSON.stringify(doc.metadata, null)}]`); + * } + * // Output: * thud [{"baz":"bar"}] + * ``` + *
+ * + *
+ * + * + *
+ * Similarity search with filter + * + * ```typescript + * const resultsWithFilter = await vectorStore.similaritySearch("thud", 1, { baz: "bar" }); + * + * for (const doc of resultsWithFilter) { + * console.log(`* ${doc.pageContent} [${JSON.stringify(doc.metadata, null)}]`); + * } + * // Output: * foo [{"baz":"bar"}] + * ``` + *
+ * + *
+ * + * + *
+ * Similarity search with score + * + * ```typescript + * const resultsWithScore = await vectorStore.similaritySearchWithScore("qux", 1); + * for (const [doc, score] of resultsWithScore) { + * console.log(`* [SIM=${score.toFixed(6)}] ${doc.pageContent} [${JSON.stringify(doc.metadata, null)}]`); + * } + * // Output: * [SIM=0.000000] qux [{"bar":"baz","baz":"bar"}] + * ``` + *
+ * + *
+ * + *
+ * As a retriever + * + * ```typescript + * const retriever = vectorStore.asRetriever({ + * searchType: "mmr", // Leave blank for standard similarity search + * k: 1, + * }); + * const resultAsRetriever = await retriever.invoke("thud"); + * console.log(resultAsRetriever); + * + * // Output: [Document({ metadata: { "baz":"bar" }, pageContent: "thud" })] + * ``` + *
+ * + *
*/ export class SupabaseVectorStore extends VectorStore { declare FilterType: SupabaseMetadata | SupabaseFilterRPCCall; diff --git a/libs/langchain-exa/src/tools.ts b/libs/langchain-exa/src/tools.ts index 6bf52ad64ede..285ea7f66d0b 100644 --- a/libs/langchain-exa/src/tools.ts +++ b/libs/langchain-exa/src/tools.ts @@ -13,7 +13,75 @@ export type ExaSearchRetrieverFields< }; /** - * Tool for the Exa search API. + * Exa search tool integration. + * + * Setup: + * Install `@langchain/exa` and `exa-js`. You'll also need an API key. + * + * ```bash + * npm install @langchain/exa exa-js + * ``` + * + * ## [Constructor args](https://api.js.langchain.com/classes/_langchain_exa.ExaSearchResults.html#constructor) + * + *
+ * Instantiate + * + * ```typescript + * import { ExaSearchResults } from "@langchain/exa"; + * import Exa from "exa-js"; + * + * const client = new Exa(process.env.EXASEARCH_API_KEY); + * + * const tool = new ExaSearchResults({ + * client, + * searchArgs: { + * numResults: 2, + * }, + * }); + * ``` + *
+ * + *
+ * + *
+ * + * Invocation + * + * ```typescript + * await tool.invoke("what is the current weather in sf?"); + * ``` + *
+ * + *
+ * + *
+ * + * Invocation with tool call + * + * ```typescript + * // This is usually generated by a model, but we'll create a tool call directly for demo purposes. + * const modelGeneratedToolCall = { + * args: { + * input: "what is the current weather in sf?", + * }, + * id: "tool_call_id", + * name: tool.name, + * type: "tool_call", + * }; + * await tool.invoke(modelGeneratedToolCall); + * ``` + * + * ```text + * ToolMessage { + * "content": "...", + * "name": "exa_search_results_json", + * "additional_kwargs": {}, + * "response_metadata": {}, + * "tool_call_id": "tool_call_id" + * } + * ``` + *
*/ export class ExaSearchResults< T extends ContentsOptions = { text: true } diff --git a/libs/langchain-pinecone/src/vectorstores.ts b/libs/langchain-pinecone/src/vectorstores.ts index b9e3be201326..3a99dbceb057 100644 --- a/libs/langchain-pinecone/src/vectorstores.ts +++ b/libs/langchain-pinecone/src/vectorstores.ts @@ -64,8 +64,137 @@ export type PineconeDeleteParams = { }; /** - * Class for managing and operating vector search applications with - * Pinecone, the cloud-native high-scale vector database + * Pinecone vector store integration. + * + * Setup: + * Install `@langchain/pinecone` and `@pinecone-database/pinecone` to pass a client in. + * + * ```bash + * npm install @langchain/pinecone @pinecone-database/pinecone + * ``` + * + * ## [Constructor args](https://api.js.langchain.com/classes/_langchain_pinecone.PineconeStore.html#constructor) + * + *
+ * Instantiate + * + * ```typescript + * import { PineconeStore } from '@langchain/pinecone'; + * // Or other embeddings + * import { OpenAIEmbeddings } from '@langchain/openai'; + * + * import { Pinecone as PineconeClient } from "@pinecone-database/pinecone"; + * + * const pinecone = new PineconeClient(); + * + * // Will automatically read the PINECONE_API_KEY and PINECONE_ENVIRONMENT env vars + * const pineconeIndex = pinecone.Index(process.env.PINECONE_INDEX!); + * + * const embeddings = new OpenAIEmbeddings({ + * model: "text-embedding-3-small", + * }); + * + * const vectorStore = await PineconeStore.fromExistingIndex(embeddings, { + * pineconeIndex, + * // Maximum number of batch requests to allow at once. Each batch is 1000 vectors. + * maxConcurrency: 5, + * // You can pass a namespace here too + * // namespace: "foo", + * }); + * ``` + *
+ * + *
+ * + *
+ * Add documents + * + * ```typescript + * import type { Document } from '@langchain/core/documents'; + * + * const document1 = { pageContent: "foo", metadata: { baz: "bar" } }; + * const document2 = { pageContent: "thud", metadata: { bar: "baz" } }; + * const document3 = { pageContent: "i will be deleted :(", metadata: {} }; + * + * const documents: Document[] = [document1, document2, document3]; + * const ids = ["1", "2", "3"]; + * await vectorStore.addDocuments(documents, { ids }); + * ``` + *
+ * + *
+ * + *
+ * Delete documents + * + * ```typescript + * await vectorStore.delete({ ids: ["3"] }); + * ``` + *
+ * + *
+ * + *
+ * Similarity search + * + * ```typescript + * const results = await vectorStore.similaritySearch("thud", 1); + * for (const doc of results) { + * console.log(`* ${doc.pageContent} [${JSON.stringify(doc.metadata, null)}]`); + * } + * // Output: * thud [{"baz":"bar"}] + * ``` + *
+ * + *
+ * + * + *
+ * Similarity search with filter + * + * ```typescript + * const resultsWithFilter = await vectorStore.similaritySearch("thud", 1, { baz: "bar" }); + * + * for (const doc of resultsWithFilter) { + * console.log(`* ${doc.pageContent} [${JSON.stringify(doc.metadata, null)}]`); + * } + * // Output: * foo [{"baz":"bar"}] + * ``` + *
+ * + *
+ * + * + *
+ * Similarity search with score + * + * ```typescript + * const resultsWithScore = await vectorStore.similaritySearchWithScore("qux", 1); + * for (const [doc, score] of resultsWithScore) { + * console.log(`* [SIM=${score.toFixed(6)}] ${doc.pageContent} [${JSON.stringify(doc.metadata, null)}]`); + * } + * // Output: * [SIM=0.000000] qux [{"bar":"baz","baz":"bar"}] + * ``` + *
+ * + *
+ * + *
+ * As a retriever + * + * ```typescript + * const retriever = vectorStore.asRetriever({ + * searchType: "mmr", // Leave blank for standard similarity search + * k: 1, + * }); + * const resultAsRetriever = await retriever.invoke("thud"); + * console.log(resultAsRetriever); + * + * // Output: [Document({ metadata: { "baz":"bar" }, pageContent: "thud" })] + * ``` + *
+ * + *
*/ export class PineconeStore extends VectorStore { declare FilterType: PineconeMetadata; From 51a4c2c35dc827b30e4f8667c51a915167868a0e Mon Sep 17 00:00:00 2001 From: Erick Friis Date: Wed, 14 Aug 2024 06:01:21 -0700 Subject: [PATCH 021/344] docs: index pages and sidebars (#6522) * docs: index pages and sidebars [wip] * x * x * Fix sidebars for doc loaders, remove deprecated docs, polish * Update index pages with custom guide links --------- Co-authored-by: jacoblee93 --- docs/core_docs/.gitignore | 21 +- docs/core_docs/docs/concepts.mdx | 8 +- .../docs/integrations/chat/azure.ipynb | 2 +- .../docs/integrations/chat/index.mdx | 66 +- .../document_loaders/file_loaders/index.mdx | 10 +- .../integrations/document_loaders/index.mdx | 18 + .../document_loaders/web_loaders/index.mdx | 12 +- .../docs/integrations/llms/index.mdx | 49 +- .../retrievers/chatgpt-retriever-plugin.mdx | 1 + .../docs/integrations/retrievers/index.mdx | 27 + .../docs/integrations/stores/index.mdx | 6 +- .../text_embedding/google_generativeai.ipynb | 2 - .../integrations/text_embedding/index.mdx | 14 + .../docs/integrations/toolkits/index.mdx | 14 + .../docs/integrations/tools/index.mdx | 20 + .../docs/integrations/vectorstores/index.mdx | 14 + docs/core_docs/sidebars.js | 252 +++--- docs/core_docs/src/theme/FeatureTables.js | 832 ++++++++++++++++++ 18 files changed, 1171 insertions(+), 197 deletions(-) create mode 100644 docs/core_docs/docs/integrations/document_loaders/index.mdx create mode 100644 docs/core_docs/docs/integrations/retrievers/index.mdx create mode 100644 docs/core_docs/docs/integrations/text_embedding/index.mdx create mode 100644 docs/core_docs/docs/integrations/toolkits/index.mdx create mode 100644 docs/core_docs/docs/integrations/tools/index.mdx create mode 100644 docs/core_docs/docs/integrations/vectorstores/index.mdx create mode 100644 docs/core_docs/src/theme/FeatureTables.js diff --git a/docs/core_docs/.gitignore b/docs/core_docs/.gitignore index b1e7aab4a4a5..c2737d8dfe94 100644 --- a/docs/core_docs/.gitignore +++ b/docs/core_docs/.gitignore @@ -348,17 +348,6 @@ docs/integrations/retrievers/self_query/hnswlib.md docs/integrations/retrievers/self_query/hnswlib.mdx docs/integrations/retrievers/self_query/chroma.md docs/integrations/retrievers/self_query/chroma.mdx -docs/integrations/document_loaders/file_loaders/unstructured.md -docs/integrations/document_loaders/file_loaders/unstructured.mdx -docs/integrations/document_loaders/file_loaders/text.md -docs/integrations/document_loaders/file_loaders/text.mdx -docs/integrations/document_loaders/file_loaders/pdf.md -docs/integrations/document_loaders/file_loaders/pdf.mdx -docs/integrations/document_loaders/file_loaders/directory.md -docs/integrations/document_loaders/file_loaders/directory.mdx -docs/integrations/document_loaders/file_loaders/csv.md -docs/integrations/document_loaders/file_loaders/csv.mdx -.vercel docs/integrations/document_loaders/web_loaders/web_puppeteer.md docs/integrations/document_loaders/web_loaders/web_puppeteer.mdx docs/integrations/document_loaders/web_loaders/web_cheerio.md @@ -369,3 +358,13 @@ docs/integrations/document_loaders/web_loaders/pdf.md docs/integrations/document_loaders/web_loaders/pdf.mdx docs/integrations/document_loaders/web_loaders/firecrawl.md docs/integrations/document_loaders/web_loaders/firecrawl.mdx +docs/integrations/document_loaders/file_loaders/unstructured.md +docs/integrations/document_loaders/file_loaders/unstructured.mdx +docs/integrations/document_loaders/file_loaders/text.md +docs/integrations/document_loaders/file_loaders/text.mdx +docs/integrations/document_loaders/file_loaders/pdf.md +docs/integrations/document_loaders/file_loaders/pdf.mdx +docs/integrations/document_loaders/file_loaders/directory.md +docs/integrations/document_loaders/file_loaders/directory.mdx +docs/integrations/document_loaders/file_loaders/csv.md +docs/integrations/document_loaders/file_loaders/csv.mdx \ No newline at end of file diff --git a/docs/core_docs/docs/concepts.mdx b/docs/core_docs/docs/concepts.mdx index 5a42901e8a08..f363cf44fd46 100644 --- a/docs/core_docs/docs/concepts.mdx +++ b/docs/core_docs/docs/concepts.mdx @@ -22,7 +22,7 @@ import useBaseUrl from "@docusaurus/useBaseUrl"; ### `@langchain/core` This package contains base abstractions of different components and ways to compose them together. -The interfaces for core components like LLMs, vectorstores, retrievers and more are defined here. +The interfaces for core components like LLMs, vector stores, retrievers and more are defined here. No third party integrations are defined here. The dependencies are kept purposefully very lightweight. @@ -30,7 +30,7 @@ The dependencies are kept purposefully very lightweight. This package contains third party integrations that are maintained by the LangChain community. Key partner packages are separated out (see below). -This contains all integrations for various components (LLMs, vectorstores, retrievers). +This contains all integrations for various components (LLMs, vector stores, retrievers). All dependencies in this package are optional to keep the package as lightweight as possible. ### Partner packages @@ -143,7 +143,7 @@ Some components LangChain implements, some components we rely on third-party int Language models that use a sequence of messages as inputs and return chat messages as outputs (as opposed to using plain text). -These are traditionally newer models (older models are generally `LLMs`, see below). +These are generally newer models (older models are generally `LLMs`, see below). Chat models support the assignment of distinct roles to conversation messages, helping to distinguish messages from the AI, users, and instructions such as system messages. Although the underlying models are messages in, message out, the LangChain wrappers also allow these models to take a string as input. @@ -480,7 +480,7 @@ The base Embeddings class in LangChain provides two methods: one for embedding d For specifics on how to use embedding models, see the [relevant how-to guides here](/docs/how_to/#embedding-models). -### Vectorstores +### Vector stores {#vectorstore} diff --git a/docs/core_docs/docs/integrations/chat/azure.ipynb b/docs/core_docs/docs/integrations/chat/azure.ipynb index a4e19b4ddc4c..20bfaf9957d1 100644 --- a/docs/core_docs/docs/integrations/chat/azure.ipynb +++ b/docs/core_docs/docs/integrations/chat/azure.ipynb @@ -21,7 +21,7 @@ "source": [ "# AzureChatOpenAI\n", "\n", - "[Azure OpenAI](https://learn.microsoft.com/en-us/azure/ai-services/openai/) is a Microsoft Azure service that provides powerful language models from OpenAI.\n", + "Azure OpenAI is a Microsoft Azure service that provides powerful language models from OpenAI.\n", "\n", "This will help you getting started with AzureChatOpenAI [chat models](/docs/concepts/#chat-models). For detailed documentation of all AzureChatOpenAI features and configurations head to the [API reference](https://api.js.langchain.com/classes/langchain_openai.AzureChatOpenAI.html).\n", "\n", diff --git a/docs/core_docs/docs/integrations/chat/index.mdx b/docs/core_docs/docs/integrations/chat/index.mdx index 23c81a22933c..0b4fa43125a5 100644 --- a/docs/core_docs/docs/integrations/chat/index.mdx +++ b/docs/core_docs/docs/integrations/chat/index.mdx @@ -6,40 +6,32 @@ hide_table_of_contents: true # Chat models -## Features (natively supported) - -All ChatModels implement the Runnable interface, which comes with default implementations of all methods, ie. `invoke`, `batch`, `stream`. This gives all ChatModels basic support for invoking, streaming and batching, which by default is implemented as below: - -- _Streaming_ support defaults to returning an `AsyncIterator` of a single value, the final result returned by the underlying ChatModel provider. This obviously doesn't give you token-by-token streaming, which requires native support from the ChatModel provider, but ensures your code that expects an iterator of tokens can work for any of our ChatModel integrations. -- _Batch_ support defaults to calling the underlying ChatModel in parallel for each input. The concurrency can be controlled with the `maxConcurrency` key in `RunnableConfig`. - -Each ChatModel integration can optionally provide native implementations to truly enable invoke, streaming or batching requests. - -Additionally, some chat models support additional ways of guaranteeing structure in their outputs by allowing you to pass in a defined schema. -[Tool calling](/docs/how_to/tool_calling) (tool calling) is one capability, and allows you to use the chat model as the LLM in certain types of agents. -Some models in LangChain have also implemented a `withStructuredOutput()` method that unifies many of these different ways of constraining output to a schema. - -The table shows, for each integration, which features have been implemented with native support. Yellow circles (🟡) indicates partial support - for example, if the model supports tool calling but not tool messages for agents. - -| Model | Stream | JSON mode | [Tool Calling](/docs/how_to/tool_calling/) | [`withStructuredOutput()`](/docs/how_to/structured_output/#the-.withstructuredoutput-method) | [Multimodal](/docs/how_to/multimodal_inputs/) | -| :---------------------- | :----: | :-------: | :----------------------------------------: | :------------------------------------------------------------------------------------------: | :-------------------------------------------: | -| BedrockChat | ✅ | ❌ | 🟡 (Bedrock Anthropic only) | 🟡 (Bedrock Anthropic only) | 🟡 (Bedrock Anthropic only) | -| ChatBedrockConverse | ✅ | ❌ | ✅ | ✅ | ✅ | -| ChatAlibabaTongyi | ❌ | ❌ | ❌ | ❌ | ❌ | -| ChatAnthropic | ✅ | ❌ | ✅ | ✅ | ✅ | -| ChatBaiduWenxin | ❌ | ❌ | ❌ | ❌ | ❌ | -| ChatCloudflareWorkersAI | ✅ | ❌ | ❌ | ❌ | ❌ | -| ChatCohere | ✅ | ❌ | ✅ | ✅ | ✅ | -| ChatFireworks | ✅ | ✅ | ✅ | ✅ | ✅ | -| ChatGoogleGenerativeAI | ✅ | ❌ | ✅ | ✅ | ✅ | -| ChatVertexAI | ✅ | ❌ | ✅ | ✅ | ✅ | -| ChatGroq | ✅ | ✅ | ✅ | ✅ | ✅ | -| ChatLlamaCpp | ✅ | ❌ | ❌ | ❌ | ❌ | -| ChatMinimax | ❌ | ❌ | ❌ | ❌ | ❌ | -| ChatMistralAI | ❌ | ✅ | ✅ | ✅ | ✅ | -| ChatOllama | ✅ | ✅ | ✅ | ✅ | ✅ | -| ChatOpenAI | ✅ | ✅ | ✅ | ✅ | ✅ | -| ChatTencentHunyuan | ✅ | ❌ | ❌ | ❌ | ❌ | -| ChatTogetherAI | ✅ | ✅ | ✅ | ✅ | ✅ | -| ChatYandexGPT | ❌ | ❌ | ❌ | ❌ | ❌ | -| ChatZhipuAI | ❌ | ❌ | ❌ | ❌ | ❌ | +[Chat models](/docs/concepts/#chat-models) are language models that use a sequence of [messages](/docs/concepts/#messages) as inputs and return messages as outputs (as opposed to using plain text). These are generally newer models. + +:::info +If you'd like to write your own chat model, see [this how-to](/docs/how_to/custom_chat). If you'd like to contribute an integration, see [Contributing integrations](/docs/contributing). +::: + +## Featured providers + +| Model | Stream | JSON mode | [Tool Calling](/docs/how_to/tool_calling/) | [`withStructuredOutput()`](/docs/how_to/structured_output/#the-.withstructuredoutput-method) | [Multimodal](/docs/how_to/multimodal_inputs/) | +| :----------------------------------------------------------------------- | :----: | :-------: | :----------------------------------------: | :------------------------------------------------------------------------------------------: | :-------------------------------------------: | +| [BedrockChat](/docs/integrations/chat/bedrock/) | ✅ | ❌ | 🟡 (Bedrock Anthropic only) | 🟡 (Bedrock Anthropic only) | 🟡 (Bedrock Anthropic only) | +| [ChatBedrockConverse](/docs/integrations/chat/bedrock_converse/) | ✅ | ❌ | ✅ | ✅ | ✅ | +| [ChatAnthropic](/docs/integrations/chat/anthropic/) | ✅ | ❌ | ✅ | ✅ | ✅ | +| [ChatCloudflareWorkersAI](/docs/integrations/chat/cloudflare_workersai/) | ✅ | ❌ | ❌ | ❌ | ❌ | +| [ChatCohere](/docs/integrations/chat/cohere/) | ✅ | ❌ | ✅ | ✅ | ✅ | +| [ChatFireworks](/docs/integrations/chat/fireworks/) | ✅ | ✅ | ✅ | ✅ | ✅ | +| [ChatGoogleGenerativeAI](/docs/integrations/chat/google_generativeai/) | ✅ | ❌ | ✅ | ✅ | ✅ | +| [ChatVertexAI](/docs/integrations/chat/google_vertex_ai/) | ✅ | ❌ | ✅ | ✅ | ✅ | +| [ChatGroq](/docs/integrations/chat/groq/) | ✅ | ✅ | ✅ | ✅ | ✅ | +| [ChatMistralAI](/docs/integrations/chat/mistral/) | ✅ | ✅ | ✅ | ✅ | ✅ | +| [ChatOllama](/docs/integrations/chat/ollama/) | ✅ | ✅ | ✅ | ✅ | ✅ | +| [ChatOpenAI](/docs/integrations/chat/openai/) | ✅ | ✅ | ✅ | ✅ | ✅ | +| [ChatTogetherAI](/docs/integrations/chat/togetherai/) | ✅ | ✅ | ✅ | ✅ | ✅ | + +## All chat models + +import { IndexTable } from "@theme/FeatureTables"; + + diff --git a/docs/core_docs/docs/integrations/document_loaders/file_loaders/index.mdx b/docs/core_docs/docs/integrations/document_loaders/file_loaders/index.mdx index 5eb2b241d08c..8006032b0374 100644 --- a/docs/core_docs/docs/integrations/document_loaders/file_loaders/index.mdx +++ b/docs/core_docs/docs/integrations/document_loaders/file_loaders/index.mdx @@ -12,6 +12,12 @@ Only available on Node.js. These loaders are used to load files given a filesystem path or a Blob object. -import DocCardList from "@theme/DocCardList"; +:::info +If you'd like to write your own document loader, see [this how-to](/docs/how_to/document_loader_custom/). If you'd like to contribute an integration, see [Contributing integrations](/docs/contributing). +::: + +import { CategoryTable, IndexTable } from "@theme/FeatureTables"; + +## All document loaders - + diff --git a/docs/core_docs/docs/integrations/document_loaders/index.mdx b/docs/core_docs/docs/integrations/document_loaders/index.mdx new file mode 100644 index 000000000000..44770ad5b1ad --- /dev/null +++ b/docs/core_docs/docs/integrations/document_loaders/index.mdx @@ -0,0 +1,18 @@ +--- +sidebar_position: 0 +--- + +# Document loaders + +[Document loaders](/docs/concepts#document-loaders) load data into LangChain's expected format for use-cases such as [retrieval-augmented generation (RAG)](/docs/tutorials/rag). + +LangChain.js categorizes document loaders in two different ways: + +- [File loaders](/docs/integrations/document_loaders/file_loaders/), which load data into LangChain formats from your local filesystem. +- [Web loaders](/docs/integrations/document_loaders/web_loaders/), which load data from remote sources. + +See the individual pages for more on each category. + +:::info +If you'd like to write your own document loader, see [this how-to](/docs/how_to/document_loader_custom/). If you'd like to contribute an integration, see [Contributing integrations](/docs/contributing). +::: diff --git a/docs/core_docs/docs/integrations/document_loaders/web_loaders/index.mdx b/docs/core_docs/docs/integrations/document_loaders/web_loaders/index.mdx index e86f3f70a99b..e9978359f156 100644 --- a/docs/core_docs/docs/integrations/document_loaders/web_loaders/index.mdx +++ b/docs/core_docs/docs/integrations/document_loaders/web_loaders/index.mdx @@ -5,8 +5,14 @@ hide_table_of_contents: true # Web Loaders -These loaders are used to load web resources. +These loaders are used to load web resources. They do not involve the local file system. -import DocCardList from "@theme/DocCardList"; +import { CategoryTable, IndexTable } from "@theme/FeatureTables"; - +:::info +If you'd like to write your own document loader, see [this how-to](/docs/how_to/document_loader_custom/). If you'd like to contribute an integration, see [Contributing integrations](/docs/contributing). +::: + +## All web loaders + + diff --git a/docs/core_docs/docs/integrations/llms/index.mdx b/docs/core_docs/docs/integrations/llms/index.mdx index c12a1441245c..b50ac95938b2 100644 --- a/docs/core_docs/docs/integrations/llms/index.mdx +++ b/docs/core_docs/docs/integrations/llms/index.mdx @@ -5,35 +5,20 @@ sidebar_class_name: hidden # LLMs - - - -## Features (natively supported) - -All LLMs implement the Runnable interface, which comes with default implementations of all methods, ie. `invoke`, `batch`, `stream`, `map`. This gives all LLMs basic support for invoking, streaming, batching and mapping requests, which by default is implemented as below: - -- _Streaming_ support defaults to returning an `AsyncIterator` of a single value, the final result returned by the underlying LLM provider. This obviously doesn't give you token-by-token streaming, which requires native support from the LLM provider, but ensures your code that expects an iterator of tokens can work for any of our LLM integrations. -- _Batch_ support defaults to calling the underlying LLM in parallel for each input. The concurrency can be controlled with the `maxConcurrency` key in `RunnableConfig`. -- _Map_ support defaults to calling `.invoke` across all instances of the array which it was called on. - -Each LLM integration can optionally provide native implementations for invoke, streaming or batch, which, for providers that support it, can be more efficient. The table shows, for each integration, which features have been implemented with native support. - -| Model | Invoke | Stream | Batch | -| :------------------- | :----: | :----: | :---: | -| AI21 | ✅ | ❌ | ✅ | -| AlephAlpha | ✅ | ❌ | ✅ | -| AzureOpenAI | ✅ | ✅ | ✅ | -| CloudflareWorkersAI | ✅ | ✅ | ✅ | -| Cohere | ✅ | ❌ | ✅ | -| Fireworks | ✅ | ✅ | ✅ | -| GooglePaLM | ✅ | ❌ | ✅ | -| HuggingFaceInference | ✅ | ❌ | ✅ | -| LlamaCpp | ✅ | ✅ | ✅ | -| Ollama | ✅ | ✅ | ✅ | -| OpenAI | ✅ | ✅ | ✅ | -| OpenAIChat | ✅ | ✅ | ✅ | -| Portkey | ✅ | ✅ | ✅ | -| Replicate | ✅ | ❌ | ✅ | -| SageMakerEndpoint | ✅ | ✅ | ✅ | -| Writer | ✅ | ❌ | ✅ | -| YandexGPT | ✅ | ❌ | ✅ | +:::caution +You are currently on a page documenting the use of [text completion models](/docs/concepts/#llms). Many of the latest and most popular models are [chat completion models](/docs/concepts/#chat-models). + +Unless you are specifically using more advanced prompting techniques, you are probably looking for [this page instead](/docs/integrations/chat/). +::: + +[LLMs](docs/concepts/#llms) are Language models that takes a string as input and returns a string. These are generally older models (newer models generally are chat models, see above). + +:::info +If you'd like to write your own LLM, see [this how-to](/docs/how_to/custom_llm). If you'd like to contribute an integration, see [Contributing integrations](/docs/contributing). +::: + +## All LLMs + +import { IndexTable } from "@theme/FeatureTables"; + + diff --git a/docs/core_docs/docs/integrations/retrievers/chatgpt-retriever-plugin.mdx b/docs/core_docs/docs/integrations/retrievers/chatgpt-retriever-plugin.mdx index 42a535462bdd..e9766ddbe0eb 100644 --- a/docs/core_docs/docs/integrations/retrievers/chatgpt-retriever-plugin.mdx +++ b/docs/core_docs/docs/integrations/retrievers/chatgpt-retriever-plugin.mdx @@ -1,5 +1,6 @@ --- hide_table_of_contents: true +sidebar_class_name: hidden --- # ChatGPT Plugin Retriever diff --git a/docs/core_docs/docs/integrations/retrievers/index.mdx b/docs/core_docs/docs/integrations/retrievers/index.mdx new file mode 100644 index 000000000000..72fd489c13ae --- /dev/null +++ b/docs/core_docs/docs/integrations/retrievers/index.mdx @@ -0,0 +1,27 @@ +--- +sidebar_position: 0 +sidebar_class_name: hidden +--- + +import { CategoryTable, IndexTable } from "@theme/FeatureTables"; + +# Retrievers + +A [retriever](/docs/concepts/#retrievers) is an interface that returns documents given an unstructured query. +It is more general than a vector store. +A retriever does not need to be able to store documents, only to return (or retrieve) them. + +Retrievers accept a string query as input and return a list of Documents. + +For specifics on how to use retrievers, see the [relevant how-to guides here](/docs/how_to/#retrievers). + +Note that all [vector stores](/docs/concepts/#vectorstores) can be [cast to retrievers](/docs/how_to/vectorstore_retriever/). +Refer to the vector store [integration docs](/docs/integrations/vectorstores/) for available vector store retrievers. + +:::info +If you'd like to write your own retriever, see [this how-to](/docs/how_to/custom_retriever/). If you'd like to contribute an integration, see [Contributing integrations](/docs/contributing). +::: + +## All retrievers + + diff --git a/docs/core_docs/docs/integrations/stores/index.mdx b/docs/core_docs/docs/integrations/stores/index.mdx index 60dbfd7414d8..c5782bb1c919 100644 --- a/docs/core_docs/docs/integrations/stores/index.mdx +++ b/docs/core_docs/docs/integrations/stores/index.mdx @@ -4,8 +4,10 @@ sidebar_class_name: hidden # Key-value stores +import { CategoryTable, IndexTable } from "@theme/FeatureTables"; + [Key-value stores](/docs/concepts/#key-value-stores) are used by other LangChain components to store and retrieve data. -import DocCardList from "@theme/DocCardList"; +## All key-value stores - + diff --git a/docs/core_docs/docs/integrations/text_embedding/google_generativeai.ipynb b/docs/core_docs/docs/integrations/text_embedding/google_generativeai.ipynb index 038614cfe1ff..d1a8e0069711 100644 --- a/docs/core_docs/docs/integrations/text_embedding/google_generativeai.ipynb +++ b/docs/core_docs/docs/integrations/text_embedding/google_generativeai.ipynb @@ -21,8 +21,6 @@ "source": [ "# GoogleGenerativeAIEmbeddings\n", "\n", - "- [ ] TODO: Make sure API reference link is correct\n", - "\n", "This will help you get started with Google Generative AI [embedding models](/docs/concepts#embedding-models) using LangChain. For detailed documentation on `GoogleGenerativeAIEmbeddings` features and configuration options, please refer to the [API reference](https://api.js.langchain.com/classes/langchain_google_genai.GoogleGenerativeAIEmbeddings.html).\n", "\n", "## Overview\n", diff --git a/docs/core_docs/docs/integrations/text_embedding/index.mdx b/docs/core_docs/docs/integrations/text_embedding/index.mdx new file mode 100644 index 000000000000..2e5b612041aa --- /dev/null +++ b/docs/core_docs/docs/integrations/text_embedding/index.mdx @@ -0,0 +1,14 @@ +--- +sidebar_position: 0 +sidebar_class_name: hidden +--- + +# Embeddings + +[Embedding models](/docs/concepts#embedding-models) create a vector representation of a piece of text. + +This page documents integrations with various model providers that allow you to use embeddings in LangChain. + +import { CategoryTable, IndexTable } from "@theme/FeatureTables"; + + diff --git a/docs/core_docs/docs/integrations/toolkits/index.mdx b/docs/core_docs/docs/integrations/toolkits/index.mdx new file mode 100644 index 000000000000..c80d2816b3ff --- /dev/null +++ b/docs/core_docs/docs/integrations/toolkits/index.mdx @@ -0,0 +1,14 @@ +--- +sidebar_position: 0 +sidebar_class_name: hidden +--- + +# Toolkits + +import { CategoryTable, IndexTable } from "@theme/FeatureTables"; + +Also see [Tools page](/docs/integrations/tools/). + +## All Toolkits + + diff --git a/docs/core_docs/docs/integrations/tools/index.mdx b/docs/core_docs/docs/integrations/tools/index.mdx new file mode 100644 index 000000000000..b1ea32d458b2 --- /dev/null +++ b/docs/core_docs/docs/integrations/tools/index.mdx @@ -0,0 +1,20 @@ +--- +sidebar_position: 0 +sidebar_class_name: hidden +--- + +# Tools and Toolkits + +import { CategoryTable, IndexTable } from "@theme/FeatureTables"; + +[Tools](/docs/concepts/#tools) are utilities designed to be called by a model: their inputs are designed to be generated by models, and their outputs are designed to be passed back to models. + +A [toolkit](/docs/concepts#toolkits) is a collection of tools meant to be used together. For a list of toolkit integrations, see [this page](/docs/integrations/toolkits/). + +:::info +If you'd like to write your own tool, see [this how-to](/docs/how_to/custom_tools/). If you'd like to contribute an integration, see [Contributing integrations](/docs/contributing). +::: + +## All Tools + + diff --git a/docs/core_docs/docs/integrations/vectorstores/index.mdx b/docs/core_docs/docs/integrations/vectorstores/index.mdx new file mode 100644 index 000000000000..a335c0aa3cbb --- /dev/null +++ b/docs/core_docs/docs/integrations/vectorstores/index.mdx @@ -0,0 +1,14 @@ +--- +sidebar_position: 0 +sidebar_class_name: hidden +--- + +# Vector stores + +A [vector store](/docs/concepts/#vectorstores) stores [embedded](/docs/concepts/#embedding-models) data and performing similarity search. + +LangChain.js integrates with a variety of vector stores. You can check out a full list below: + +import { CategoryTable, IndexTable } from "@theme/FeatureTables"; + + diff --git a/docs/core_docs/sidebars.js b/docs/core_docs/sidebars.js index 0f2dc6dbc4f6..e4297b057712 100644 --- a/docs/core_docs/sidebars.js +++ b/docs/core_docs/sidebars.js @@ -119,11 +119,12 @@ module.exports = { { type: "category", label: "Chat models", - collapsed: true, + collapsible: false, items: [ { type: "autogenerated", dirName: "integrations/chat", + className: "hidden", }, ], link: { @@ -134,11 +135,12 @@ module.exports = { { type: "category", label: "LLMs", - collapsed: true, + collapsible: false, items: [ { type: "autogenerated", dirName: "integrations/llms", + className: "hidden", }, ], link: { @@ -149,16 +151,17 @@ module.exports = { { type: "category", label: "Embedding models", - collapsed: true, + collapsible: false, items: [ { type: "autogenerated", dirName: "integrations/text_embedding", + className: "hidden", }, ], link: { - type: "generated-index", - slug: "integrations/text_embedding", + type: "doc", + id: "integrations/text_embedding/index", }, }, { @@ -167,183 +170,226 @@ module.exports = { collapsed: true, items: [ { - type: "autogenerated", - dirName: "integrations/document_loaders", + type: "category", + label: "File loaders", + collapsible: false, + items: [ + { + type: "autogenerated", + dirName: "integrations/document_loaders/file_loaders", + className: "hidden", + }, + ], + link: { + type: "doc", + id: "integrations/document_loaders/file_loaders/index", + }, }, - ], - link: { - type: "generated-index", - slug: "integrations/document_loaders", - }, - }, - { - type: "category", - label: "Document transformers", - collapsed: true, - items: [ { - type: "autogenerated", - dirName: "integrations/document_transformers", - }, - { - type: "autogenerated", - dirName: "integrations/document_compressors", + type: "category", + label: "Web loaders", + collapsible: false, + items: [ + { + type: "autogenerated", + dirName: "integrations/document_loaders/web_loaders", + className: "hidden", + }, + ], + link: { + type: "doc", + id: "integrations/document_loaders/web_loaders/index", + }, }, ], link: { - type: "generated-index", - slug: "integrations/document_transformers", + type: "doc", + id: "integrations/document_loaders/index", }, }, { type: "category", label: "Vector stores", - collapsed: true, + collapsible: false, items: [ { type: "autogenerated", dirName: "integrations/vectorstores", + className: "hidden", }, ], link: { - type: "generated-index", - slug: "integrations/vectorstores", + type: "doc", + id: "integrations/vectorstores/index", }, }, { type: "category", label: "Retrievers", - collapsed: true, + collapsible: false, items: [ { type: "autogenerated", dirName: "integrations/retrievers", + className: "hidden", }, ], link: { - type: "generated-index", - slug: "integrations/retrievers", + type: "doc", + id: "integrations/retrievers/index", }, }, { type: "category", - label: "Tools", - collapsed: true, + label: "Tools/Toolkits", + collapsible: false, items: [ { type: "autogenerated", dirName: "integrations/tools", + className: "hidden", }, ], link: { - type: "generated-index", - slug: "integrations/tools", + type: "doc", + id: "integrations/tools/index", }, }, { type: "category", label: "Toolkits", - collapsed: true, + collapsible: false, + className: "hidden", items: [ { type: "autogenerated", dirName: "integrations/toolkits", + className: "hidden", }, ], link: { - type: "generated-index", - slug: "integrations/toolkits", + type: "doc", + id: "integrations/tools/index", }, }, { type: "category", - label: "Memory", - collapsed: true, + label: "Key-value stores", + collapsible: false, items: [ { type: "autogenerated", - dirName: "integrations/memory", + dirName: "integrations/stores", + className: "hidden", }, ], link: { - type: "generated-index", - slug: "integrations/memory", + type: "doc", + id: "integrations/stores/index", }, }, { type: "category", - label: "Graphs", + label: "Other", collapsed: true, items: [ { - type: "autogenerated", - dirName: "integrations/graphs", + type: "category", + label: "Document transformers", + collapsible: false, + items: [ + { + type: "autogenerated", + dirName: "integrations/document_transformers", + className: "hidden", + }, + ], + link: { + type: "generated-index", + slug: "integrations/document_transformers", + }, }, - ], - link: { - type: "generated-index", - slug: "integrations/graphs", - }, - }, - { - type: "category", - label: "Callbacks", - collapsed: true, - items: [ { - type: "autogenerated", - dirName: "integrations/callbacks", + type: "category", + label: "Graphs", + collapsible: false, + items: [ + { + type: "autogenerated", + dirName: "integrations/graphs", + className: "hidden", + }, + ], + link: { + type: "generated-index", + slug: "integrations/graphs", + }, }, - ], - link: { - type: "generated-index", - slug: "integrations/callbacks", - }, - }, - { - type: "category", - label: "Chat loaders", - collapsed: true, - items: [ { - type: "autogenerated", - dirName: "integrations/chat_loaders", + type: "category", + label: "Memory", + collapsible: false, + items: [ + { + type: "autogenerated", + dirName: "integrations/memory", + className: "hidden", + }, + ], + link: { + type: "generated-index", + slug: "integrations/memory", + }, }, - ], - link: { - type: "generated-index", - slug: "integrations/chat_loaders", - }, - }, - { - type: "category", - label: "Adapters", - collapsed: true, - items: [ { - type: "autogenerated", - dirName: "integrations/adapters", + type: "category", + label: "Callbacks", + collapsible: false, + items: [ + { + type: "autogenerated", + dirName: "integrations/callbacks", + className: "hidden", + }, + ], + link: { + type: "generated-index", + slug: "integrations/callbacks", + }, }, - ], - link: { - type: "generated-index", - slug: "integrations/adapters", - }, - }, - { - type: "category", - label: "Key-value stores", - collapsed: true, - items: [ { - type: "autogenerated", - dirName: "integrations/stores", + type: "category", + label: "Chat loaders", + collapsible: false, + items: [ + { + type: "autogenerated", + dirName: "integrations/chat_loaders", + className: "hidden", + }, + ], + link: { + type: "generated-index", + slug: "integrations/chat_loaders", + }, + }, + { + type: "category", + label: "Adapters", + collapsible: false, + items: [ + { + type: "autogenerated", + dirName: "integrations/adapters", + className: "hidden", + }, + ], + link: { + type: "generated-index", + slug: "integrations/adapters", + }, }, ], - link: { - type: "doc", - id: "integrations/stores/index", - }, }, ], link: { diff --git a/docs/core_docs/src/theme/FeatureTables.js b/docs/core_docs/src/theme/FeatureTables.js new file mode 100644 index 000000000000..a1c66228b238 --- /dev/null +++ b/docs/core_docs/src/theme/FeatureTables.js @@ -0,0 +1,832 @@ +/* eslint-disable import/no-extraneous-dependencies */ +/* eslint-disable prefer-template */ +import React from "react"; +import { useCurrentSidebarCategory } from "@docusaurus/theme-common"; +import { useDocById } from "@docusaurus/theme-common/internal"; + +const FEATURE_TABLES = { + chat: { + link: "/docs/integrations/chat", + columns: [ + { + title: "Provider", + formatter: (item) => {item.name}, + }, + { + title: Tool calling, + formatter: (item) => (item.tool_calling ? "✅" : "❌"), + }, + { + title: Structured output, + formatter: (item) => (item.structured_output ? "✅" : "❌"), + }, + { + title: "JSON mode", + formatter: (item) => (item.json_mode ? "✅" : "❌"), + }, + { title: "Local", formatter: (item) => (item.local ? "✅" : "❌") }, + { + title: Multimodal, + formatter: (item) => (item.multimodal ? "✅" : "❌"), + }, + { + title: "Package", + formatter: (item) => {item.package}, + }, + ], + items: [ + { + name: "ChatAnthropic", + package: "langchain-anthropic", + link: "anthropic/", + structured_output: true, + tool_calling: true, + json_mode: false, + multimodal: true, + local: false, + apiLink: + "https://api.python.langchain.com/en/latest/chat_models/langchain_anthropic.chat_models.ChatAnthropic.html#langchain_anthropic.chat_models.ChatAnthropic", + }, + { + name: "ChatMistralAI", + package: "langchain-mistralai", + link: "mistralai/", + structured_output: true, + tool_calling: true, + json_mode: false, + multimodal: false, + local: false, + apiLink: + "https://api.python.langchain.com/en/latest/chat_models/langchain_mistralai.chat_models.ChatMistralAI.html#langchain_mistralai.chat_models.ChatMistralAI", + }, + { + name: "ChatFireworks", + package: "langchain-fireworks", + link: "fireworks/", + structured_output: true, + tool_calling: true, + json_mode: true, + multimodal: false, + local: false, + apiLink: + "https://api.python.langchain.com/en/latest/chat_models/langchain_fireworks.chat_models.ChatFireworks.html#langchain_fireworks.chat_models.ChatFireworks", + }, + { + name: "AzureChatOpenAI", + package: "langchain-openai", + link: "azure_chat_openai/", + structured_output: true, + tool_calling: true, + json_mode: true, + multimodal: true, + local: false, + apiLink: + "https://api.python.langchain.com/en/latest/chat_models/langchain_openai.chat_models.azure.AzureChatOpenAI.html#langchain_openai.chat_models.azure.AzureChatOpenAI", + }, + { + name: "ChatOpenAI", + package: "langchain-openai", + link: "openai/", + structured_output: true, + tool_calling: true, + json_mode: true, + multimodal: true, + local: false, + apiLink: + "https://api.python.langchain.com/en/latest/chat_models/langchain_openai.chat_models.base.ChatOpenAI.html#langchain_openai.chat_models.base.ChatOpenAI", + }, + { + name: "ChatTogether", + package: "langchain-together", + link: "together/", + structured_output: true, + tool_calling: true, + json_mode: true, + multimodal: false, + local: false, + apiLink: + "https://api.python.langchain.com/en/latest/chat_models/langchain_together.chat_models.ChatTogether.html#langchain_together.chat_models.ChatTogether", + }, + { + name: "ChatVertexAI", + package: "langchain-google-vertexai", + link: "google_vertex_ai_palm/", + structured_output: true, + tool_calling: true, + json_mode: false, + multimodal: true, + local: false, + apiLink: + "https://api.python.langchain.com/en/latest/chat_models/langchain_google_vertexai.chat_models.ChatVertexAI.html#langchain_google_vertexai.chat_models.ChatVertexAI", + }, + { + name: "ChatGoogleGenerativeAI", + package: "langchain-google-genai", + link: "google_generative_ai/", + structured_output: true, + tool_calling: true, + json_mode: false, + multimodal: true, + local: false, + apiLink: + "https://api.python.langchain.com/en/latest/chat_models/langchain_google_genai.chat_models.ChatGoogleGenerativeAI.html#langchain_google_genai.chat_models.ChatGoogleGenerativeAI", + }, + { + name: "ChatGroq", + package: "langchain-groq", + link: "groq/", + structured_output: true, + tool_calling: true, + json_mode: true, + multimodal: false, + local: false, + apiLink: + "https://api.python.langchain.com/en/latest/chat_models/langchain_groq.chat_models.ChatGroq.html#langchain_groq.chat_models.ChatGroq", + }, + { + name: "ChatCohere", + package: "langchain-cohere", + link: "cohere/", + structured_output: true, + tool_calling: true, + json_mode: false, + multimodal: false, + local: false, + apiLink: + "https://api.python.langchain.com/en/latest/chat_models/langchain_cohere.chat_models.ChatCohere.html#langchain_cohere.chat_models.ChatCohere", + }, + { + name: "ChatBedrock", + package: "langchain-aws", + link: "bedrock/", + structured_output: true, + tool_calling: true, + json_mode: false, + multimodal: false, + local: false, + apiLink: + "https://api.python.langchain.com/en/latest/chat_models/langchain_aws.chat_models.bedrock.ChatBedrock.html#langchain_aws.chat_models.bedrock.ChatBedrock", + }, + { + name: "ChatHuggingFace", + package: "langchain-huggingface", + link: "huggingface/", + structured_output: true, + tool_calling: true, + json_mode: false, + multimodal: false, + local: true, + apiLink: + "https://api.python.langchain.com/en/latest/chat_models/langchain_huggingface.chat_models.huggingface.ChatHuggingFace.html#langchain_huggingface.chat_models.huggingface.ChatHuggingFace", + }, + { + name: "ChatNVIDIA", + package: "langchain-nvidia-ai-endpoints", + link: "nvidia_ai_endpoints/", + structured_output: true, + tool_calling: true, + json_mode: false, + multimodal: false, + local: true, + apiLink: + "https://api.python.langchain.com/en/latest/chat_models/langchain_nvidia_ai_endpoints.chat_models.ChatNVIDIA.html#langchain_nvidia_ai_endpoints.chat_models.ChatNVIDIA", + }, + { + name: "ChatOllama", + package: "langchain-ollama", + link: "ollama/", + structured_output: true, + tool_calling: true, + json_mode: true, + multimodal: false, + local: true, + apiLink: + "https://api.python.langchain.com/en/latest/chat_models/langchain_ollama.chat_models.ChatOllama.html#langchain_ollama.chat_models.ChatOllama", + }, + { + name: "ChatLlamaCpp", + package: "langchain-community", + link: "llamacpp", + structured_output: true, + tool_calling: true, + json_mode: false, + multimodal: false, + local: true, + apiLink: + "https://api.python.langchain.com/en/latest/chat_models/langchain_community.chat_models.llamacpp.ChatLlamaCpp.html#langchain_community.chat_models.llamacpp.ChatLlamaCpp", + }, + { + name: "ChatAI21", + package: "langchain-ai21", + link: "ai21", + structured_output: true, + tool_calling: true, + json_mode: false, + multimodal: false, + local: false, + apiLink: + "https://api.python.langchain.com/en/latest/chat_models/langchain_ai21.chat_models.ChatAI21.html#langchain_ai21.chat_models.ChatAI21", + }, + { + name: "ChatUpstage", + package: "langchain-upstage", + link: "upstage", + structured_output: true, + tool_calling: true, + json_mode: false, + multimodal: false, + local: false, + apiLink: + "https://api.python.langchain.com/en/latest/chat_models/langchain_upstage.chat_models.ChatUpstage.html#langchain_upstage.chat_models.ChatUpstage", + }, + ], + }, + llms: { + link: "/docs/integrations/llms", + columns: [ + { + title: "Provider", + formatter: (item) => {item.name}, + }, + { + title: "Package", + formatter: (item) => {item.package}, + }, + ], + items: [ + { + name: "AI21LLM", + link: "ai21", + package: "langchain-ai21", + apiLink: + "https://api.python.langchain.com/en/latest/llms/langchain_ai21.llms.AI21LLM.html#langchain_ai21.llms.AI21LLM", + }, + { + name: "AnthropicLLM", + link: "anthropic", + package: "langchain-anthropic", + apiLink: + "https://api.python.langchain.com/en/latest/llms/langchain_anthropic.llms.AnthropicLLM.html#langchain_anthropic.llms.AnthropicLLM", + }, + { + name: "AzureOpenAI", + link: "azure_openai", + package: "langchain-openai", + apiLink: + "https://api.python.langchain.com/en/latest/llms/langchain_openai.llms.azure.AzureOpenAI.html#langchain_openai.llms.azure.AzureOpenAI", + }, + { + name: "BedrockLLM", + link: "bedrock", + package: "langchain-aws", + apiLink: + "https://api.python.langchain.com/en/latest/llms/langchain_aws.llms.bedrock.BedrockLLM.html#langchain_aws.llms.bedrock.BedrockLLM", + }, + { + name: "CohereLLM", + link: "cohere", + package: "langchain-cohere", + apiLink: + "https://api.python.langchain.com/en/latest/llms/langchain_cohere.llms.Cohere.html#langchain_cohere.llms.Cohere", + }, + { + name: "FireworksLLM", + link: "fireworks", + package: "langchain-fireworks", + apiLink: + "https://api.python.langchain.com/en/latest/llms/langchain_fireworks.llms.Fireworks.html#langchain_fireworks.llms.Fireworks", + }, + { + name: "OllamaLLM", + link: "ollama", + package: "langchain-ollama", + apiLink: + "https://api.python.langchain.com/en/latest/llms/langchain_ollama.llms.OllamaLLM.html#langchain_ollama.llms.OllamaLLM", + }, + { + name: "OpenAILLM", + link: "openai", + package: "langchain-openai", + apiLink: + "https://api.python.langchain.com/en/latest/llms/langchain_openai.llms.base.OpenAI.html#langchain_openai.llms.base.OpenAI", + }, + { + name: "TogetherLLM", + link: "together", + package: "langchain-together", + apiLink: + "https://api.python.langchain.com/en/latest/llms/langchain_together.llms.Together.html#langchain_together.llms.Together", + }, + { + name: "VertexAILLM", + link: "google_vertexai", + package: "langchain-google_vertexai", + apiLink: + "https://api.python.langchain.com/en/latest/llms/langchain_google_vertexai.llms.VertexAI.html#langchain_google_vertexai.llms.VertexAI", + }, + ], + }, + text_embedding: { + link: "/docs/integrations/text_embedding", + columns: [ + { + title: "Provider", + formatter: (item) => {item.name}, + }, + { + title: "Package", + formatter: (item) => {item.package}, + }, + ], + items: [ + { + name: "AzureOpenAI", + link: "azureopenai", + package: "langchain-openai", + apiLink: + "https://api.python.langchain.com/en/latest/embeddings/langchain_openai.embeddings.azure.AzureOpenAIEmbeddings.html#langchain_openai.embeddings.azure.AzureOpenAIEmbeddings", + }, + { + name: "Ollama", + link: "ollama", + package: "langchain-ollama", + apiLink: + "https://api.python.langchain.com/en/latest/embeddings/langchain_ollama.embeddings.OllamaEmbeddings.html#langchain_ollama.embeddings.OllamaEmbeddings", + }, + { + name: "AI21", + link: "ai21", + package: "langchain-ai21", + apiLink: + "https://api.python.langchain.com/en/latest/embeddings/langchain_ai21.embeddings.AI21Embeddings.html#langchain_ai21.embeddings.AI21Embeddings", + }, + { + name: "Fake", + link: "fake", + package: "langchain-core", + apiLink: + "https://api.python.langchain.com/en/latest/embeddings/langchain_core.embeddings.fake.FakeEmbeddings.html#langchain_core.embeddings.fake.FakeEmbeddings", + }, + { + name: "OpenAI", + link: "openai", + package: "langchain-openai", + apiLink: + "https://api.python.langchain.com/en/latest/chat_models/langchain_openai.chat_models.base.ChatOpenAI.html#langchain_openai.chat_models.base.ChatOpenAI", + }, + { + name: "Together", + link: "together", + package: "langchain-together", + apiLink: + "https://api.python.langchain.com/en/latest/embeddings/langchain_together.embeddings.TogetherEmbeddings.html#langchain_together.embeddings.TogetherEmbeddings", + }, + { + name: "Fireworks", + link: "fireworks", + package: "langchain-fireworks", + apiLink: + "https://api.python.langchain.com/en/latest/embeddings/langchain_fireworks.embeddings.FireworksEmbeddings.html#langchain_fireworks.embeddings.FireworksEmbeddings", + }, + { + name: "MistralAI", + link: "mistralai", + package: "langchain-mistralai", + apiLink: + "https://api.python.langchain.com/en/latest/embeddings/langchain_mistralai.embeddings.MistralAIEmbeddings.html#langchain_mistralai.embeddings.MistralAIEmbeddings", + }, + { + name: "Cohere", + link: "cohere", + package: "langchain-cohere", + apiLink: + "https://api.python.langchain.com/en/latest/embeddings/langchain_cohere.embeddings.CohereEmbeddings.html#langchain_cohere.embeddings.CohereEmbeddings", + }, + ], + }, + document_retrievers: { + link: "docs/integrations/retrievers", + columns: [ + { + title: "Retriever", + formatter: (item) => {item.name}, + }, + { + title: "Self-host", + formatter: (item) => (item.selfHost ? "✅" : "❌"), + }, + { + title: "Cloud offering", + formatter: (item) => (item.cloudOffering ? "✅" : "❌"), + }, + { + title: "Package", + formatter: (item) => {item.package}, + }, + ], + items: [ + { + name: "AmazonKnowledgeBasesRetriever", + link: "bedrock", + selfHost: false, + cloudOffering: true, + apiLink: + "https://api.python.langchain.com/en/latest/retrievers/langchain_aws.retrievers.bedrock.AmazonKnowledgeBasesRetriever.html", + package: "langchain_aws", + }, + { + name: "AzureAISearchRetriever", + link: "azure_ai_search", + selfHost: false, + cloudOffering: true, + apiLink: + "https://api.python.langchain.com/en/latest/retrievers/langchain_community.retrievers.azure_ai_search.AzureAISearchRetriever.html", + package: "langchain_community", + }, + { + name: "ElasticsearchRetriever", + link: "elasticsearch_retriever", + selfHost: true, + cloudOffering: true, + apiLink: + "https://api.python.langchain.com/en/latest/retrievers/langchain_elasticsearch.retrievers.ElasticsearchRetriever.html", + package: "langchain_elasticsearch", + }, + { + name: "MilvusCollectionHybridSearchRetriever", + link: "milvus_hybrid_search", + selfHost: true, + cloudOffering: false, + apiLink: + "https://api.python.langchain.com/en/latest/retrievers/langchain_milvus.retrievers.milvus_hybrid_search.MilvusCollectionHybridSearchRetriever.html", + package: "langchain_milvus", + }, + { + name: "VertexAISearchRetriever", + link: "google_vertex_ai_search", + selfHost: false, + cloudOffering: true, + apiLink: + "https://api.python.langchain.com/en/latest/vertex_ai_search/langchain_google_community.vertex_ai_search.VertexAISearchRetriever.html", + package: "langchain_google_community", + }, + ], + }, + external_retrievers: { + link: "docs/integrations/retrievers", + columns: [ + { + title: "Retriever", + formatter: (item) => {item.name}, + }, + { title: "Source", formatter: (item) => item.source }, + { + title: "Package", + formatter: (item) => {item.package}, + }, + ], + items: [ + { + name: "ArxivRetriever", + link: "arxiv", + source: ( + <> + Scholarly articles on arxiv.org + + ), + apiLink: + "https://api.python.langchain.com/en/latest/retrievers/langchain_community.retrievers.arxiv.ArxivRetriever.html", + package: "langchain_community", + }, + { + name: "TavilySearchAPIRetriever", + link: "tavily", + source: "Internet search", + apiLink: + "https://api.python.langchain.com/en/latest/retrievers/langchain_community.retrievers.tavily_search_api.TavilySearchAPIRetriever.html", + package: "langchain_community", + }, + { + name: "WikipediaRetriever", + link: "wikipedia", + source: ( + <> + Wikipedia articles + + ), + apiLink: + "https://api.python.langchain.com/en/latest/retrievers/langchain_community.retrievers.wikipedia.WikipediaRetriever.html", + package: "langchain_community", + }, + ], + }, + document_loaders: { + link: "docs/integrations/loaders", + columns: [], + items: [], + }, + vectorstores: { + link: "docs/integrations/vectorstores", + columns: [ + { + title: "Vectorstore", + formatter: (item) => {item.name}, + }, + { + title: "Delete by ID", + formatter: (item) => (item.deleteById ? "✅" : "❌"), + }, + { + title: "Filtering", + formatter: (item) => (item.filtering ? "✅" : "❌"), + }, + { + title: "Search by Vector", + formatter: (item) => (item.searchByVector ? "✅" : "❌"), + }, + { + title: "Search with score", + formatter: (item) => (item.searchWithScore ? "✅" : "❌"), + }, + { title: "Async", formatter: (item) => (item.async ? "✅" : "❌") }, + { + title: "Passes Standard Tests", + formatter: (item) => (item.passesStandardTests ? "✅" : "❌"), + }, + { + title: "Multi Tenancy", + formatter: (item) => (item.multiTenancy ? "✅" : "❌"), + }, + { + title: "IDs in add Documents", + formatter: (item) => (item.idsInAddDocuments ? "✅" : "❌"), + }, + { + title: "Local/Cloud", + formatter: (item) => (item.local ? "Local" : "Cloud"), + }, + ], + items: [ + { + name: "AstraDBVectorStore", + link: "astradb", + deleteById: true, + filtering: true, + searchByVector: true, + searchWithScore: true, + async: true, + passesStandardTests: false, + multiTenancy: false, + local: true, + idsInAddDocuments: false, + }, + { + name: "Chroma", + link: "chroma", + deleteById: true, + filtering: true, + searchByVector: true, + searchWithScore: true, + async: true, + passesStandardTests: false, + multiTenancy: false, + local: true, + idsInAddDocuments: false, + }, + { + name: "Clickhouse", + link: "clickhouse", + deleteById: true, + filtering: true, + searchByVector: false, + searchWithScore: true, + async: false, + passesStandardTests: false, + multiTenancy: false, + local: true, + idsInAddDocuments: false, + }, + { + name: "CouchbaseVectorStore", + link: "couchbase", + deleteById: true, + filtering: true, + searchByVector: false, + searchWithScore: true, + async: true, + passesStandardTests: false, + multiTenancy: false, + local: true, + idsInAddDocuments: false, + }, + { + name: "ElasticsearchStore", + link: "elasticsearch", + deleteById: true, + filtering: true, + searchByVector: true, + searchWithScore: false, + async: true, + passesStandardTests: false, + multiTenancy: false, + local: true, + idsInAddDocuments: false, + }, + { + name: "FAISS", + link: "faiss", + deleteById: true, + filtering: true, + searchByVector: true, + searchWithScore: true, + async: true, + passesStandardTests: false, + multiTenancy: false, + local: true, + idsInAddDocuments: false, + }, + { + name: "InMemoryVectorStore", + link: "in_memory", + deleteById: true, + filtering: true, + searchByVector: false, + searchWithScore: true, + async: true, + passesStandardTests: false, + multiTenancy: false, + local: true, + idsInAddDocuments: false, + }, + { + name: "Milvus", + link: "milvus", + deleteById: true, + filtering: true, + searchByVector: false, + searchWithScore: true, + async: true, + passesStandardTests: false, + multiTenancy: false, + local: true, + idsInAddDocuments: false, + }, + { + name: "MongoDBAtlasVectorSearch", + link: "mongodb_atlas", + deleteById: true, + filtering: true, + searchByVector: false, + searchWithScore: false, + async: true, + passesStandardTests: false, + multiTenancy: false, + local: true, + idsInAddDocuments: false, + }, + { + name: "PGVector", + link: "pg_vector", + deleteById: true, + filtering: true, + searchByVector: true, + searchWithScore: true, + async: true, + passesStandardTests: false, + multiTenancy: false, + local: true, + idsInAddDocuments: false, + }, + { + name: "PineconeVectorStore", + link: "pinecone", + deleteById: true, + filtering: true, + searchByVector: true, + searchWithScore: false, + async: true, + passesStandardTests: false, + multiTenancy: false, + local: true, + idsInAddDocuments: false, + }, + { + name: "QdrantVectorStore", + link: "qdrant", + deleteById: true, + filtering: true, + searchByVector: true, + searchWithScore: true, + async: true, + passesStandardTests: false, + multiTenancy: false, + local: true, + idsInAddDocuments: false, + }, + { + name: "Redis", + link: "redis", + deleteById: true, + filtering: true, + searchByVector: true, + searchWithScore: true, + async: true, + passesStandardTests: false, + multiTenancy: false, + local: true, + idsInAddDocuments: false, + }, + ], + }, +}; + +const DEPRECATED_DOC_IDS = [ + "integrations/chat/anthropic_tools", + "integrations/chat/baidu_wenxin", + "integrations/chat/google_palm", + "integrations/chat/ni_bittensor", + "integrations/llms/google_palm", + "integrations/llms/ni_bittensor", + "integrations/llms/prompt_layer_openai", + "integrations/text_embedding/google_palm", + "integrations/retrievers/chatgpt-retriever-plugin", + "integrations/tools/aiplugin-tool", + "integrations/tools/zapier_agent", +]; + +function toTable(columns, items) { + const headers = columns.map((col) => col.title); + return ( + + + + {headers.map((header, i) => ( + // eslint-disable-next-line react/no-array-index-key + + ))} + + + + {items.map((item, i) => ( + // eslint-disable-next-line react/no-array-index-key + + {columns.map((col, j) => ( + // eslint-disable-next-line react/no-array-index-key + + ))} + + ))} + +
{header}
{col.formatter(item)}
+ ); +} + +export function CategoryTable({ category }) { + const cat = FEATURE_TABLES[category]; + const rtn = toTable(cat.columns, cat.items); + return rtn; +} + +export function ItemTable({ category, item }) { + const cat = FEATURE_TABLES[category]; + const row = cat.items.find((i) => i.name === item); + if (!row) { + throw new Error(`Item ${item} not found in category ${category}`); + } + const rtn = toTable(cat.columns, [row]); + return rtn; +} + +function truncate(str, n) { + return str.length > n ? str.substring(0, n - 1) + "..." : str; +} + +export function IndexTable() { + const { items } = useCurrentSidebarCategory(); + + const rows = items + .filter( + (item) => + !item.docId?.endsWith?.("/index") && + !DEPRECATED_DOC_IDS.includes(item.docId) + ) + .map((item) => ({ + ...item, + // eslint-disable-next-line react-hooks/rules-of-hooks + description: useDocById(item.docId ?? undefined)?.description, + })); + const rtn = toTable( + [ + { + title: "Name", + formatter: (item) => {item.label}, + }, + { + title: "Description", + formatter: (item) => truncate(item.description ?? "", 70), + }, + ], + rows + ); + return rtn; +} From 3d28b8beccd3bfc130500e4e66894504aa54d4c3 Mon Sep 17 00:00:00 2001 From: Jacob Lee Date: Wed, 14 Aug 2024 06:53:58 -0700 Subject: [PATCH 022/344] Bump version, relax types (#6526) --- libs/langchain-anthropic/package.json | 2 +- .../src/utils/message_inputs.ts | 19 +++++++++---------- yarn.lock | 11 +++++------ 3 files changed, 15 insertions(+), 17 deletions(-) diff --git a/libs/langchain-anthropic/package.json b/libs/langchain-anthropic/package.json index bdde0965807b..1312d40f96a9 100644 --- a/libs/langchain-anthropic/package.json +++ b/libs/langchain-anthropic/package.json @@ -35,7 +35,7 @@ "author": "LangChain", "license": "MIT", "dependencies": { - "@anthropic-ai/sdk": "^0.22.0", + "@anthropic-ai/sdk": "^0.25.2", "@langchain/core": ">=0.2.21 <0.3.0", "fast-xml-parser": "^4.4.1", "zod": "^3.22.4", diff --git a/libs/langchain-anthropic/src/utils/message_inputs.ts b/libs/langchain-anthropic/src/utils/message_inputs.ts index 8c45d38fea83..b6fe6523df4a 100644 --- a/libs/langchain-anthropic/src/utils/message_inputs.ts +++ b/libs/langchain-anthropic/src/utils/message_inputs.ts @@ -11,7 +11,10 @@ import { isAIMessage, } from "@langchain/core/messages"; import { ToolCall } from "@langchain/core/messages/tool"; -import { AnthropicMessageParam, AnthropicToolResponse } from "../types.js"; +import { + AnthropicMessageCreateParams, + AnthropicToolResponse, +} from "../types.js"; function _formatImage(imageUrl: string) { const regex = /^data:(image\/.+);base64,(.+)$/; @@ -179,16 +182,12 @@ function _formatContent(content: MessageContent) { * @param messages The base messages to format as a prompt. * @returns The formatted prompt. */ -export function _formatMessagesForAnthropic(messages: BaseMessage[]): { - system?: string; - messages: AnthropicMessageParam[]; -} { +export function _formatMessagesForAnthropic( + messages: BaseMessage[] +): AnthropicMessageCreateParams { const mergedMessages = _mergeMessages(messages); - let system: string | undefined; + let system; if (mergedMessages.length > 0 && mergedMessages[0]._getType() === "system") { - if (typeof messages[0].content !== "string") { - throw new Error("System message content must be a string."); - } system = messages[0].content; } const conversationMessages = @@ -256,5 +255,5 @@ export function _formatMessagesForAnthropic(messages: BaseMessage[]): { return { messages: formattedMessages, system, - }; + } as AnthropicMessageCreateParams; } diff --git a/yarn.lock b/yarn.lock index 036030d4dab3..232e9bc34685 100644 --- a/yarn.lock +++ b/yarn.lock @@ -211,9 +211,9 @@ __metadata: languageName: node linkType: hard -"@anthropic-ai/sdk@npm:^0.22.0": - version: 0.22.0 - resolution: "@anthropic-ai/sdk@npm:0.22.0" +"@anthropic-ai/sdk@npm:^0.25.2": + version: 0.25.2 + resolution: "@anthropic-ai/sdk@npm:0.25.2" dependencies: "@types/node": ^18.11.18 "@types/node-fetch": ^2.6.4 @@ -222,8 +222,7 @@ __metadata: form-data-encoder: 1.7.2 formdata-node: ^4.3.2 node-fetch: ^2.6.7 - web-streams-polyfill: ^3.2.1 - checksum: f09fc6ea1f5f68483fd2dbdc1ab78a0914d7a3f39fd4a921f5a04b8959ae82aa210372fa2dbff2ee78143d37fb408e7b7d61e0023e7ea31d5fa1f4873f371af8 + checksum: b38f6a43f6f678e49f1b53226cc55654c23cf0fd1902cf3fcf98c0ae78f4c229518964f4cb31bc39da41925c806e7f4cc7ec14c511d387f07db3136b111bc744 languageName: node linkType: hard @@ -10852,7 +10851,7 @@ __metadata: version: 0.0.0-use.local resolution: "@langchain/anthropic@workspace:libs/langchain-anthropic" dependencies: - "@anthropic-ai/sdk": ^0.22.0 + "@anthropic-ai/sdk": ^0.25.2 "@jest/globals": ^29.5.0 "@langchain/core": ">=0.2.21 <0.3.0" "@langchain/scripts": ~0.0.20 From 229d92e994c492987bb3623bdb14813c9673bbab Mon Sep 17 00:00:00 2001 From: Jacob Lee Date: Wed, 14 Aug 2024 06:58:32 -0700 Subject: [PATCH 023/344] anthropic[patch]: Release 0.2.15 (#6528) --- libs/langchain-anthropic/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libs/langchain-anthropic/package.json b/libs/langchain-anthropic/package.json index 1312d40f96a9..314dc51b5b7d 100644 --- a/libs/langchain-anthropic/package.json +++ b/libs/langchain-anthropic/package.json @@ -1,6 +1,6 @@ { "name": "@langchain/anthropic", - "version": "0.2.14", + "version": "0.2.15", "description": "Anthropic integrations for LangChain.js", "type": "module", "engines": { From c92dfb5dd23e3d426d23d337659014c700c0a58d Mon Sep 17 00:00:00 2001 From: Jacob Lee Date: Wed, 14 Aug 2024 07:24:39 -0700 Subject: [PATCH 024/344] docs[patch]: Fix typos (#6530) * Typos * Deprecate --- docs/core_docs/docs/integrations/llms/index.mdx | 2 +- docs/core_docs/docs/integrations/vectorstores/index.mdx | 2 +- docs/core_docs/src/theme/FeatureTables.js | 2 ++ 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/docs/core_docs/docs/integrations/llms/index.mdx b/docs/core_docs/docs/integrations/llms/index.mdx index b50ac95938b2..646b79f19c62 100644 --- a/docs/core_docs/docs/integrations/llms/index.mdx +++ b/docs/core_docs/docs/integrations/llms/index.mdx @@ -11,7 +11,7 @@ You are currently on a page documenting the use of [text completion models](/doc Unless you are specifically using more advanced prompting techniques, you are probably looking for [this page instead](/docs/integrations/chat/). ::: -[LLMs](docs/concepts/#llms) are Language models that takes a string as input and returns a string. These are generally older models (newer models generally are chat models, see above). +[LLMs](docs/concepts/#llms) are language models that takes a string as input and return a string as output. :::info If you'd like to write your own LLM, see [this how-to](/docs/how_to/custom_llm). If you'd like to contribute an integration, see [Contributing integrations](/docs/contributing). diff --git a/docs/core_docs/docs/integrations/vectorstores/index.mdx b/docs/core_docs/docs/integrations/vectorstores/index.mdx index a335c0aa3cbb..eb93d1f64953 100644 --- a/docs/core_docs/docs/integrations/vectorstores/index.mdx +++ b/docs/core_docs/docs/integrations/vectorstores/index.mdx @@ -5,7 +5,7 @@ sidebar_class_name: hidden # Vector stores -A [vector store](/docs/concepts/#vectorstores) stores [embedded](/docs/concepts/#embedding-models) data and performing similarity search. +A [vector store](/docs/concepts/#vectorstores) stores [embedded](/docs/concepts/#embedding-models) data and performs similarity search. LangChain.js integrates with a variety of vector stores. You can check out a full list below: diff --git a/docs/core_docs/src/theme/FeatureTables.js b/docs/core_docs/src/theme/FeatureTables.js index a1c66228b238..1870361af2b9 100644 --- a/docs/core_docs/src/theme/FeatureTables.js +++ b/docs/core_docs/src/theme/FeatureTables.js @@ -745,6 +745,8 @@ const DEPRECATED_DOC_IDS = [ "integrations/chat/baidu_wenxin", "integrations/chat/google_palm", "integrations/chat/ni_bittensor", + "integrations/chat/ollama_functions", + "integrations/chat/prompt_layer_openai", "integrations/llms/google_palm", "integrations/llms/ni_bittensor", "integrations/llms/prompt_layer_openai", From 9303967f0d02ee24c1c3cc209dcb69ca2aa91967 Mon Sep 17 00:00:00 2001 From: Jacob Lee Date: Wed, 14 Aug 2024 09:56:09 -0700 Subject: [PATCH 025/344] docs[patch]: Add Anthropic prompt caching docs (#6531) * Adds integration test * Adds docs * Update * Fix --- .../docs/integrations/chat/anthropic.ipynb | 458 ++++++++++++++++++ .../src/tests/chat_models.int.test.ts | 350 ++++++++++++- 2 files changed, 807 insertions(+), 1 deletion(-) diff --git a/docs/core_docs/docs/integrations/chat/anthropic.ipynb b/docs/core_docs/docs/integrations/chat/anthropic.ipynb index 862b6a736ae9..7cf4bbb7c6d0 100644 --- a/docs/core_docs/docs/integrations/chat/anthropic.ipynb +++ b/docs/core_docs/docs/integrations/chat/anthropic.ipynb @@ -473,6 +473,464 @@ "await llmWithCustomHeaders.invoke(\"Why is the sky blue?\");" ] }, + { + "cell_type": "markdown", + "id": "3c5e6d7a", + "metadata": {}, + "source": [ + "## Prompt caching\n", + "\n", + "```{=mdx}\n", + "\n", + ":::caution Compatibility\n", + "This feature is currently in beta.\n", + ":::\n", + "\n", + "```\n", + "\n", + "Anthropic supports [caching parts of your prompt](https://docs.anthropic.com/en/docs/build-with-claude/prompt-caching) in order to reduce costs for use-cases that require long context. You can cache tools and both entire messages and individual blocks.\n", + "\n", + "The initial request containing one or more blocks or tool definitions with a `\"cache_control\": { \"type\": \"ephemeral\" }` field will automatically cache that part of the prompt. This initial caching step will cost extra, but subsequent requests will be billed at a reduced rate. The cache has a lifetime of 5 minutes, but this is refereshed each time the cache is hit.\n", + "\n", + "There is also currently a minimum cacheable prompt length, which varies according to model. You can see this information [here](https://docs.anthropic.com/en/docs/build-with-claude/prompt-caching#structuring-your-prompt).\n", + "\n", + "This currently requires you to initialize your model with a beta header. Here's an example of caching part of a system message that contains the LangChain [conceptual docs](/docs/concepts/):" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "5e02b056", + "metadata": {}, + "outputs": [], + "source": [ + "let CACHED_TEXT = \"...\";" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "id": "bba739ed", + "metadata": {}, + "outputs": [], + "source": [ + "// @lc-docs-hide-cell\n", + "\n", + "CACHED_TEXT = `## Components\n", + "\n", + "LangChain provides standard, extendable interfaces and external integrations for various components useful for building with LLMs.\n", + "Some components LangChain implements, some components we rely on third-party integrations for, and others are a mix.\n", + "\n", + "### Chat models\n", + "\n", + "\n", + "\n", + "Language models that use a sequence of messages as inputs and return chat messages as outputs (as opposed to using plain text).\n", + "These are generally newer models (older models are generally \\`LLMs\\`, see below).\n", + "Chat models support the assignment of distinct roles to conversation messages, helping to distinguish messages from the AI, users, and instructions such as system messages.\n", + "\n", + "Although the underlying models are messages in, message out, the LangChain wrappers also allow these models to take a string as input.\n", + "This gives them the same interface as LLMs (and simpler to use).\n", + "When a string is passed in as input, it will be converted to a \\`HumanMessage\\` under the hood before being passed to the underlying model.\n", + "\n", + "LangChain does not host any Chat Models, rather we rely on third party integrations.\n", + "\n", + "We have some standardized parameters when constructing ChatModels:\n", + "\n", + "- \\`model\\`: the name of the model\n", + "\n", + "Chat Models also accept other parameters that are specific to that integration.\n", + "\n", + ":::important\n", + "Some chat models have been fine-tuned for **tool calling** and provide a dedicated API for it.\n", + "Generally, such models are better at tool calling than non-fine-tuned models, and are recommended for use cases that require tool calling.\n", + "Please see the [tool calling section](/docs/concepts/#functiontool-calling) for more information.\n", + ":::\n", + "\n", + "For specifics on how to use chat models, see the [relevant how-to guides here](/docs/how_to/#chat-models).\n", + "\n", + "#### Multimodality\n", + "\n", + "Some chat models are multimodal, accepting images, audio and even video as inputs.\n", + "These are still less common, meaning model providers haven't standardized on the \"best\" way to define the API.\n", + "Multimodal outputs are even less common. As such, we've kept our multimodal abstractions fairly light weight\n", + "and plan to further solidify the multimodal APIs and interaction patterns as the field matures.\n", + "\n", + "In LangChain, most chat models that support multimodal inputs also accept those values in OpenAI's content blocks format.\n", + "So far this is restricted to image inputs. For models like Gemini which support video and other bytes input, the APIs also support the native, model-specific representations.\n", + "\n", + "For specifics on how to use multimodal models, see the [relevant how-to guides here](/docs/how_to/#multimodal).\n", + "\n", + "### LLMs\n", + "\n", + "\n", + "\n", + ":::caution\n", + "Pure text-in/text-out LLMs tend to be older or lower-level. Many popular models are best used as [chat completion models](/docs/concepts/#chat-models),\n", + "even for non-chat use cases.\n", + "\n", + "You are probably looking for [the section above instead](/docs/concepts/#chat-models).\n", + ":::\n", + "\n", + "Language models that takes a string as input and returns a string.\n", + "These are traditionally older models (newer models generally are [Chat Models](/docs/concepts/#chat-models), see above).\n", + "\n", + "Although the underlying models are string in, string out, the LangChain wrappers also allow these models to take messages as input.\n", + "This gives them the same interface as [Chat Models](/docs/concepts/#chat-models).\n", + "When messages are passed in as input, they will be formatted into a string under the hood before being passed to the underlying model.\n", + "\n", + "LangChain does not host any LLMs, rather we rely on third party integrations.\n", + "\n", + "For specifics on how to use LLMs, see the [relevant how-to guides here](/docs/how_to/#llms).\n", + "\n", + "### Message types\n", + "\n", + "Some language models take an array of messages as input and return a message.\n", + "There are a few different types of messages.\n", + "All messages have a \\`role\\`, \\`content\\`, and \\`response_metadata\\` property.\n", + "\n", + "The \\`role\\` describes WHO is saying the message.\n", + "LangChain has different message classes for different roles.\n", + "\n", + "The \\`content\\` property describes the content of the message.\n", + "This can be a few different things:\n", + "\n", + "- A string (most models deal this type of content)\n", + "- A List of objects (this is used for multi-modal input, where the object contains information about that input type and that input location)\n", + "\n", + "#### HumanMessage\n", + "\n", + "This represents a message from the user.\n", + "\n", + "#### AIMessage\n", + "\n", + "This represents a message from the model. In addition to the \\`content\\` property, these messages also have:\n", + "\n", + "**\\`response_metadata\\`**\n", + "\n", + "The \\`response_metadata\\` property contains additional metadata about the response. The data here is often specific to each model provider.\n", + "This is where information like log-probs and token usage may be stored.\n", + "\n", + "**\\`tool_calls\\`**\n", + "\n", + "These represent a decision from an language model to call a tool. They are included as part of an \\`AIMessage\\` output.\n", + "They can be accessed from there with the \\`.tool_calls\\` property.\n", + "\n", + "This property returns a list of \\`ToolCall\\`s. A \\`ToolCall\\` is an object with the following arguments:\n", + "\n", + "- \\`name\\`: The name of the tool that should be called.\n", + "- \\`args\\`: The arguments to that tool.\n", + "- \\`id\\`: The id of that tool call.\n", + "\n", + "#### SystemMessage\n", + "\n", + "This represents a system message, which tells the model how to behave. Not every model provider supports this.\n", + "\n", + "#### ToolMessage\n", + "\n", + "This represents the result of a tool call. In addition to \\`role\\` and \\`content\\`, this message has:\n", + "\n", + "- a \\`tool_call_id\\` field which conveys the id of the call to the tool that was called to produce this result.\n", + "- an \\`artifact\\` field which can be used to pass along arbitrary artifacts of the tool execution which are useful to track but which should not be sent to the model.\n", + "\n", + "#### (Legacy) FunctionMessage\n", + "\n", + "This is a legacy message type, corresponding to OpenAI's legacy function-calling API. \\`ToolMessage\\` should be used instead to correspond to the updated tool-calling API.\n", + "\n", + "This represents the result of a function call. In addition to \\`role\\` and \\`content\\`, this message has a \\`name\\` parameter which conveys the name of the function that was called to produce this result.\n", + "\n", + "### Prompt templates\n", + "\n", + "\n", + "\n", + "Prompt templates help to translate user input and parameters into instructions for a language model.\n", + "This can be used to guide a model's response, helping it understand the context and generate relevant and coherent language-based output.\n", + "\n", + "Prompt Templates take as input an object, where each key represents a variable in the prompt template to fill in.\n", + "\n", + "Prompt Templates output a PromptValue. This PromptValue can be passed to an LLM or a ChatModel, and can also be cast to a string or an array of messages.\n", + "The reason this PromptValue exists is to make it easy to switch between strings and messages.\n", + "\n", + "There are a few different types of prompt templates:\n", + "\n", + "#### String PromptTemplates\n", + "\n", + "These prompt templates are used to format a single string, and generally are used for simpler inputs.\n", + "For example, a common way to construct and use a PromptTemplate is as follows:\n", + "\n", + "\\`\\`\\`typescript\n", + "import { PromptTemplate } from \"@langchain/core/prompts\";\n", + "\n", + "const promptTemplate = PromptTemplate.fromTemplate(\n", + " \"Tell me a joke about {topic}\"\n", + ");\n", + "\n", + "await promptTemplate.invoke({ topic: \"cats\" });\n", + "\\`\\`\\`\n", + "\n", + "#### ChatPromptTemplates\n", + "\n", + "These prompt templates are used to format an array of messages. These \"templates\" consist of an array of templates themselves.\n", + "For example, a common way to construct and use a ChatPromptTemplate is as follows:\n", + "\n", + "\\`\\`\\`typescript\n", + "import { ChatPromptTemplate } from \"@langchain/core/prompts\";\n", + "\n", + "const promptTemplate = ChatPromptTemplate.fromMessages([\n", + " [\"system\", \"You are a helpful assistant\"],\n", + " [\"user\", \"Tell me a joke about {topic}\"],\n", + "]);\n", + "\n", + "await promptTemplate.invoke({ topic: \"cats\" });\n", + "\\`\\`\\`\n", + "\n", + "In the above example, this ChatPromptTemplate will construct two messages when called.\n", + "The first is a system message, that has no variables to format.\n", + "The second is a HumanMessage, and will be formatted by the \\`topic\\` variable the user passes in.\n", + "\n", + "#### MessagesPlaceholder\n", + "\n", + "\n", + "\n", + "This prompt template is responsible for adding an array of messages in a particular place.\n", + "In the above ChatPromptTemplate, we saw how we could format two messages, each one a string.\n", + "But what if we wanted the user to pass in an array of messages that we would slot into a particular spot?\n", + "This is how you use MessagesPlaceholder.\n", + "\n", + "\\`\\`\\`typescript\n", + "import {\n", + " ChatPromptTemplate,\n", + " MessagesPlaceholder,\n", + "} from \"@langchain/core/prompts\";\n", + "import { HumanMessage } from \"@langchain/core/messages\";\n", + "\n", + "const promptTemplate = ChatPromptTemplate.fromMessages([\n", + " [\"system\", \"You are a helpful assistant\"],\n", + " new MessagesPlaceholder(\"msgs\"),\n", + "]);\n", + "\n", + "promptTemplate.invoke({ msgs: [new HumanMessage({ content: \"hi!\" })] });\n", + "\\`\\`\\`\n", + "\n", + "This will produce an array of two messages, the first one being a system message, and the second one being the HumanMessage we passed in.\n", + "If we had passed in 5 messages, then it would have produced 6 messages in total (the system message plus the 5 passed in).\n", + "This is useful for letting an array of messages be slotted into a particular spot.\n", + "\n", + "An alternative way to accomplish the same thing without using the \\`MessagesPlaceholder\\` class explicitly is:\n", + "\n", + "\\`\\`\\`typescript\n", + "const promptTemplate = ChatPromptTemplate.fromMessages([\n", + " [\"system\", \"You are a helpful assistant\"],\n", + " [\"placeholder\", \"{msgs}\"], // <-- This is the changed part\n", + "]);\n", + "\\`\\`\\`\n", + "\n", + "For specifics on how to use prompt templates, see the [relevant how-to guides here](/docs/how_to/#prompt-templates).\n", + "\n", + "### Example Selectors\n", + "\n", + "One common prompting technique for achieving better performance is to include examples as part of the prompt.\n", + "This gives the language model concrete examples of how it should behave.\n", + "Sometimes these examples are hardcoded into the prompt, but for more advanced situations it may be nice to dynamically select them.\n", + "Example Selectors are classes responsible for selecting and then formatting examples into prompts.\n", + "\n", + "For specifics on how to use example selectors, see the [relevant how-to guides here](/docs/how_to/#example-selectors).\n", + "\n", + "### Output parsers\n", + "\n", + "\n", + "\n", + ":::note\n", + "\n", + "The information here refers to parsers that take a text output from a model try to parse it into a more structured representation.\n", + "More and more models are supporting function (or tool) calling, which handles this automatically.\n", + "It is recommended to use function/tool calling rather than output parsing.\n", + "See documentation for that [here](/docs/concepts/#function-tool-calling).\n", + "\n", + ":::\n", + "\n", + "Responsible for taking the output of a model and transforming it to a more suitable format for downstream tasks.\n", + "Useful when you are using LLMs to generate structured data, or to normalize output from chat models and LLMs.\n", + "\n", + "There are two main methods an output parser must implement:\n", + "\n", + "- \"Get format instructions\": A method which returns a string containing instructions for how the output of a language model should be formatted.\n", + "- \"Parse\": A method which takes in a string (assumed to be the response from a language model) and parses it into some structure.\n", + "\n", + "And then one optional one:\n", + "\n", + "- \"Parse with prompt\": A method which takes in a string (assumed to be the response from a language model) and a prompt (assumed to be the prompt that generated such a response) and parses it into some structure. The prompt is largely provided in the event the OutputParser wants to retry or fix the output in some way, and needs information from the prompt to do so.\n", + "\n", + "Output parsers accept a string or \\`BaseMessage\\` as input and can return an arbitrary type.\n", + "\n", + "LangChain has many different types of output parsers. This is a list of output parsers LangChain supports. The table below has various pieces of information:\n", + "\n", + "**Name**: The name of the output parser\n", + "\n", + "**Supports Streaming**: Whether the output parser supports streaming.\n", + "\n", + "**Input Type**: Expected input type. Most output parsers work on both strings and messages, but some (like OpenAI Functions) need a message with specific arguments.\n", + "\n", + "**Output Type**: The output type of the object returned by the parser.\n", + "\n", + "**Description**: Our commentary on this output parser and when to use it.\n", + "\n", + "The current date is ${new Date().toISOString()}`;\n", + "\n", + "// Noop statement to hide output\n", + "void 0;" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "6e47de9b", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "USAGE: {\n", + " input_tokens: 19,\n", + " cache_creation_input_tokens: 2925,\n", + " cache_read_input_tokens: 0,\n", + " output_tokens: 327\n", + "}\n" + ] + } + ], + "source": [ + "import { ChatAnthropic } from \"@langchain/anthropic\";\n", + "import { HumanMessage, SystemMessage } from \"@langchain/core/messages\";\n", + "\n", + "const modelWithCaching = new ChatAnthropic({\n", + " model: \"claude-3-haiku-20240307\",\n", + " clientOptions: {\n", + " defaultHeaders: {\n", + " \"anthropic-beta\": \"prompt-caching-2024-07-31\",\n", + " },\n", + " },\n", + "});\n", + "\n", + "const LONG_TEXT = `You are a pirate. Always respond in pirate dialect.\n", + "\n", + "Use the following as context when answering questions:\n", + "\n", + "${CACHED_TEXT}`;\n", + "\n", + "const messages = [\n", + " new SystemMessage({\n", + " content: [\n", + " {\n", + " type: \"text\",\n", + " text: LONG_TEXT,\n", + " // Tell Anthropic to cache this block\n", + " cache_control: { type: \"ephemeral\" },\n", + " },\n", + " ],\n", + " }),\n", + " new HumanMessage({\n", + " content: \"What types of messages are supported in LangChain?\",\n", + " }),\n", + "];\n", + "\n", + "const res = await modelWithCaching.invoke(messages);\n", + "\n", + "console.log(\"USAGE:\", res.response_metadata.usage);" + ] + }, + { + "cell_type": "markdown", + "id": "826d95b6", + "metadata": {}, + "source": [ + "We can see that there's a new field called `cache_creation_input_tokens` in the raw usage field returned from Anthropic.\n", + "\n", + "If we use the same messages again, we can see that the long text's input tokens are read from the cache:" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "5d264f8b", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "USAGE: {\n", + " input_tokens: 19,\n", + " cache_creation_input_tokens: 0,\n", + " cache_read_input_tokens: 2925,\n", + " output_tokens: 250\n", + "}\n" + ] + } + ], + "source": [ + "const res2 = await modelWithCaching.invoke(messages);\n", + "\n", + "console.log(\"USAGE:\", res2.response_metadata.usage);" + ] + }, + { + "cell_type": "markdown", + "id": "fc6bba1b", + "metadata": {}, + "source": [ + "### Tool caching\n", + "\n", + "You can also cache tools by setting the same `\"cache_control\": { \"type\": \"ephemeral\" }` within a tool definition. This currently requires you to bind a tool in [Anthropic's raw tool format](https://docs.anthropic.com/en/docs/build-with-claude/tool-use) Here's an example:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "9c7c5eaf", + "metadata": {}, + "outputs": [], + "source": [ + "const SOME_LONG_DESCRIPTION = \"...\";\n", + "\n", + "// Tool in Anthropic format\n", + "const anthropicTools = [{\n", + " name: \"get_weather\",\n", + " description: SOME_LONG_DESCRIPTION,\n", + " input_schema: {\n", + " type: \"object\",\n", + " properties: {\n", + " location: {\n", + " type: \"string\",\n", + " description: \"Location to get the weather for\",\n", + " },\n", + " unit: {\n", + " type: \"string\",\n", + " description: \"Temperature unit to return\",\n", + " },\n", + " },\n", + " required: [\"location\"],\n", + " },\n", + " // Tell Anthropic to cache this tool\n", + " cache_control: { type: \"ephemeral\" },\n", + "}]\n", + "\n", + "const modelWithCachedTools = modelWithCaching.bindTools(anthropicTools);\n", + "\n", + "await modelWithCachedTools.invoke(\"what is the weather in SF?\");" + ] + }, + { + "cell_type": "markdown", + "id": "5d000dd9", + "metadata": {}, + "source": [ + "\n", + "\n", + "For more on how prompt caching works, see [Anthropic's docs](https://docs.anthropic.com/en/docs/build-with-claude/prompt-caching#how-prompt-caching-works)." + ] + }, { "cell_type": "markdown", "id": "3a5bb5ca-c3ae-4a58-be67-2cd18574b9a3", diff --git a/libs/langchain-anthropic/src/tests/chat_models.int.test.ts b/libs/langchain-anthropic/src/tests/chat_models.int.test.ts index d83a06e8fb28..29d12a22dcb5 100644 --- a/libs/langchain-anthropic/src/tests/chat_models.int.test.ts +++ b/libs/langchain-anthropic/src/tests/chat_models.int.test.ts @@ -1,7 +1,11 @@ /* eslint-disable no-process-env */ import { expect, test } from "@jest/globals"; -import { AIMessageChunk, HumanMessage } from "@langchain/core/messages"; +import { + AIMessageChunk, + HumanMessage, + SystemMessage, +} from "@langchain/core/messages"; import { ChatPromptValue } from "@langchain/core/prompt_values"; import { PromptTemplate, @@ -387,3 +391,347 @@ test("id is supplied when streaming", async () => { expect(finalChunk.id).toBeDefined(); expect(finalChunk.id).not.toEqual(""); }); + +test("system prompt caching", async () => { + const CACHED_TEXT = `## Components + +LangChain provides standard, extendable interfaces and external integrations for various components useful for building with LLMs. +Some components LangChain implements, some components we rely on third-party integrations for, and others are a mix. + +### Chat models + + + +Language models that use a sequence of messages as inputs and return chat messages as outputs (as opposed to using plain text). +These are generally newer models (older models are generally \`LLMs\`, see below). +Chat models support the assignment of distinct roles to conversation messages, helping to distinguish messages from the AI, users, and instructions such as system messages. + +Although the underlying models are messages in, message out, the LangChain wrappers also allow these models to take a string as input. +This gives them the same interface as LLMs (and simpler to use). +When a string is passed in as input, it will be converted to a \`HumanMessage\` under the hood before being passed to the underlying model. + +LangChain does not host any Chat Models, rather we rely on third party integrations. + +We have some standardized parameters when constructing ChatModels: + +- \`model\`: the name of the model + +Chat Models also accept other parameters that are specific to that integration. + +:::important +Some chat models have been fine-tuned for **tool calling** and provide a dedicated API for it. +Generally, such models are better at tool calling than non-fine-tuned models, and are recommended for use cases that require tool calling. +Please see the [tool calling section](/docs/concepts/#functiontool-calling) for more information. +::: + +For specifics on how to use chat models, see the [relevant how-to guides here](/docs/how_to/#chat-models). + +#### Multimodality + +Some chat models are multimodal, accepting images, audio and even video as inputs. +These are still less common, meaning model providers haven't standardized on the "best" way to define the API. +Multimodal outputs are even less common. As such, we've kept our multimodal abstractions fairly light weight +and plan to further solidify the multimodal APIs and interaction patterns as the field matures. + +In LangChain, most chat models that support multimodal inputs also accept those values in OpenAI's content blocks format. +So far this is restricted to image inputs. For models like Gemini which support video and other bytes input, the APIs also support the native, model-specific representations. + +For specifics on how to use multimodal models, see the [relevant how-to guides here](/docs/how_to/#multimodal). + +### LLMs + + + +:::caution +Pure text-in/text-out LLMs tend to be older or lower-level. Many popular models are best used as [chat completion models](/docs/concepts/#chat-models), +even for non-chat use cases. + +You are probably looking for [the section above instead](/docs/concepts/#chat-models). +::: + +Language models that takes a string as input and returns a string. +These are traditionally older models (newer models generally are [Chat Models](/docs/concepts/#chat-models), see above). + +Although the underlying models are string in, string out, the LangChain wrappers also allow these models to take messages as input. +This gives them the same interface as [Chat Models](/docs/concepts/#chat-models). +When messages are passed in as input, they will be formatted into a string under the hood before being passed to the underlying model. + +LangChain does not host any LLMs, rather we rely on third party integrations. + +For specifics on how to use LLMs, see the [relevant how-to guides here](/docs/how_to/#llms). + +### Message types + +Some language models take an array of messages as input and return a message. +There are a few different types of messages. +All messages have a \`role\`, \`content\`, and \`response_metadata\` property. + +The \`role\` describes WHO is saying the message. +LangChain has different message classes for different roles. + +The \`content\` property describes the content of the message. +This can be a few different things: + +- A string (most models deal this type of content) +- A List of objects (this is used for multi-modal input, where the object contains information about that input type and that input location) + +#### HumanMessage + +This represents a message from the user. + +#### AIMessage + +This represents a message from the model. In addition to the \`content\` property, these messages also have: + +**\`response_metadata\`** + +The \`response_metadata\` property contains additional metadata about the response. The data here is often specific to each model provider. +This is where information like log-probs and token usage may be stored. + +**\`tool_calls\`** + +These represent a decision from an language model to call a tool. They are included as part of an \`AIMessage\` output. +They can be accessed from there with the \`.tool_calls\` property. + +This property returns a list of \`ToolCall\`s. A \`ToolCall\` is an object with the following arguments: + +- \`name\`: The name of the tool that should be called. +- \`args\`: The arguments to that tool. +- \`id\`: The id of that tool call. + +#### SystemMessage + +This represents a system message, which tells the model how to behave. Not every model provider supports this. + +#### ToolMessage + +This represents the result of a tool call. In addition to \`role\` and \`content\`, this message has: + +- a \`tool_call_id\` field which conveys the id of the call to the tool that was called to produce this result. +- an \`artifact\` field which can be used to pass along arbitrary artifacts of the tool execution which are useful to track but which should not be sent to the model. + +#### (Legacy) FunctionMessage + +This is a legacy message type, corresponding to OpenAI's legacy function-calling API. \`ToolMessage\` should be used instead to correspond to the updated tool-calling API. + +This represents the result of a function call. In addition to \`role\` and \`content\`, this message has a \`name\` parameter which conveys the name of the function that was called to produce this result. + +### Prompt templates + + + +Prompt templates help to translate user input and parameters into instructions for a language model. +This can be used to guide a model's response, helping it understand the context and generate relevant and coherent language-based output. + +Prompt Templates take as input an object, where each key represents a variable in the prompt template to fill in. + +Prompt Templates output a PromptValue. This PromptValue can be passed to an LLM or a ChatModel, and can also be cast to a string or an array of messages. +The reason this PromptValue exists is to make it easy to switch between strings and messages. + +There are a few different types of prompt templates: + +#### String PromptTemplates + +These prompt templates are used to format a single string, and generally are used for simpler inputs. +For example, a common way to construct and use a PromptTemplate is as follows: + +\`\`\`typescript +import { PromptTemplate } from "@langchain/core/prompts"; + +const promptTemplate = PromptTemplate.fromTemplate( + "Tell me a joke about {topic}" +); + +await promptTemplate.invoke({ topic: "cats" }); +\`\`\` + +#### ChatPromptTemplates + +These prompt templates are used to format an array of messages. These "templates" consist of an array of templates themselves. +For example, a common way to construct and use a ChatPromptTemplate is as follows: + +\`\`\`typescript +import { ChatPromptTemplate } from "@langchain/core/prompts"; + +const promptTemplate = ChatPromptTemplate.fromMessages([ + ["system", "You are a helpful assistant"], + ["user", "Tell me a joke about {topic}"], +]); + +await promptTemplate.invoke({ topic: "cats" }); +\`\`\` + +In the above example, this ChatPromptTemplate will construct two messages when called. +The first is a system message, that has no variables to format. +The second is a HumanMessage, and will be formatted by the \`topic\` variable the user passes in. + +#### MessagesPlaceholder + + + +This prompt template is responsible for adding an array of messages in a particular place. +In the above ChatPromptTemplate, we saw how we could format two messages, each one a string. +But what if we wanted the user to pass in an array of messages that we would slot into a particular spot? +This is how you use MessagesPlaceholder. + +\`\`\`typescript +import { + ChatPromptTemplate, + MessagesPlaceholder, +} from "@langchain/core/prompts"; +import { HumanMessage } from "@langchain/core/messages"; + +const promptTemplate = ChatPromptTemplate.fromMessages([ + ["system", "You are a helpful assistant"], + new MessagesPlaceholder("msgs"), +]); + +promptTemplate.invoke({ msgs: [new HumanMessage({ content: "hi!" })] }); +\`\`\` + +This will produce an array of two messages, the first one being a system message, and the second one being the HumanMessage we passed in. +If we had passed in 5 messages, then it would have produced 6 messages in total (the system message plus the 5 passed in). +This is useful for letting an array of messages be slotted into a particular spot. + +An alternative way to accomplish the same thing without using the \`MessagesPlaceholder\` class explicitly is: + +\`\`\`typescript +const promptTemplate = ChatPromptTemplate.fromMessages([ + ["system", "You are a helpful assistant"], + ["placeholder", "{msgs}"], // <-- This is the changed part +]); +\`\`\` + +For specifics on how to use prompt templates, see the [relevant how-to guides here](/docs/how_to/#prompt-templates). + +### Example Selectors + +One common prompting technique for achieving better performance is to include examples as part of the prompt. +This gives the language model concrete examples of how it should behave. +Sometimes these examples are hardcoded into the prompt, but for more advanced situations it may be nice to dynamically select them. +Example Selectors are classes responsible for selecting and then formatting examples into prompts. + +For specifics on how to use example selectors, see the [relevant how-to guides here](/docs/how_to/#example-selectors). + +### Output parsers + + + +:::note + +The information here refers to parsers that take a text output from a model try to parse it into a more structured representation. +More and more models are supporting function (or tool) calling, which handles this automatically. +It is recommended to use function/tool calling rather than output parsing. +See documentation for that [here](/docs/concepts/#function-tool-calling). + +::: + +Responsible for taking the output of a model and transforming it to a more suitable format for downstream tasks. +Useful when you are using LLMs to generate structured data, or to normalize output from chat models and LLMs. + +There are two main methods an output parser must implement: + +- "Get format instructions": A method which returns a string containing instructions for how the output of a language model should be formatted. +- "Parse": A method which takes in a string (assumed to be the response from a language model) and parses it into some structure. + +And then one optional one: + +- "Parse with prompt": A method which takes in a string (assumed to be the response from a language model) and a prompt (assumed to be the prompt that generated such a response) and parses it into some structure. The prompt is largely provided in the event the OutputParser wants to retry or fix the output in some way, and needs information from the prompt to do so. + +Output parsers accept a string or \`BaseMessage\` as input and can return an arbitrary type. + +LangChain has many different types of output parsers. This is a list of output parsers LangChain supports. The table below has various pieces of information: + +**Name**: The name of the output parser + +**Supports Streaming**: Whether the output parser supports streaming. + +**Input Type**: Expected input type. Most output parsers work on both strings and messages, but some (like OpenAI Functions) need a message with specific arguments. + +**Output Type**: The output type of the object returned by the parser. + +**Description**: Our commentary on this output parser and when to use it. + +The current date is ${new Date().toISOString()}`; + + const model = new ChatAnthropic({ + model: "claude-3-haiku-20240307", + clientOptions: { + defaultHeaders: { + "anthropic-beta": "prompt-caching-2024-07-31", + }, + }, + }); + const messages = [ + new SystemMessage({ + content: [ + { + type: "text", + text: `You are a pirate. Always respond in pirate dialect.\nUse the following as context when answering questions: ${CACHED_TEXT}`, + cache_control: { type: "ephemeral" }, + }, + ], + }), + new HumanMessage({ + content: "What types of messages are supported in LangChain?", + }), + ]; + const res = await model.invoke(messages); + expect( + res.response_metadata.usage.cache_creation_input_tokens + ).toBeGreaterThan(0); + expect(res.response_metadata.usage.cache_read_input_tokens).toBe(0); + const res2 = await model.invoke(messages); + expect(res2.response_metadata.usage.cache_creation_input_tokens).toBe(0); + expect(res2.response_metadata.usage.cache_read_input_tokens).toBeGreaterThan( + 0 + ); +}); + +// TODO: Add proper test with long tool content +test.skip("tool caching", async () => { + const model = new ChatAnthropic({ + model: "claude-3-haiku-20240307", + clientOptions: { + defaultHeaders: { + "anthropic-beta": "prompt-caching-2024-07-31", + }, + }, + }).bindTools([ + { + name: "get_weather", + description: "Get the weather for a specific location", + input_schema: { + type: "object", + properties: { + location: { + type: "string", + description: "Location to get the weather for", + }, + unit: { + type: "string", + description: "Temperature unit to return", + }, + }, + required: ["location"], + }, + cache_control: { type: "ephemeral" }, + }, + ]); + const messages = [ + new HumanMessage({ + content: "What is the weather in Regensburg?", + }), + ]; + const res = await model.invoke(messages); + console.log(res); + expect( + res.response_metadata.usage.cache_creation_input_tokens + ).toBeGreaterThan(0); + expect(res.response_metadata.usage.cache_read_input_tokens).toBe(0); + const res2 = await model.invoke(messages); + expect(res2.response_metadata.usage.cache_creation_input_tokens).toBe(0); + expect(res2.response_metadata.usage.cache_read_input_tokens).toBeGreaterThan( + 0 + ); +}); From 8dd6e48fd61973fd1521f5dae9bdb2d63c76f224 Mon Sep 17 00:00:00 2001 From: Jacob Lee Date: Thu, 15 Aug 2024 05:54:13 -0700 Subject: [PATCH 026/344] docs[patch]: Adds ChatOpenAI streamUsage docs on disabling stream usage (#6534) * Adds ChatOpenAI streamUsage docs on disabling stream usage * Fix notebook --- .../docs/integrations/chat/openai.ipynb | 30 +++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/docs/core_docs/docs/integrations/chat/openai.ipynb b/docs/core_docs/docs/integrations/chat/openai.ipynb index 9b832eba98ad..eb829b093865 100644 --- a/docs/core_docs/docs/integrations/chat/openai.ipynb +++ b/docs/core_docs/docs/integrations/chat/openai.ipynb @@ -305,6 +305,36 @@ "await llmWithCustomHeaders.invoke(\"Hi there!\");" ] }, + { + "cell_type": "markdown", + "id": "7af61d1d", + "metadata": {}, + "source": [ + "## Disabling streaming usage metadata\n", + "\n", + "Some proxies or third-party providers present largely the same API interface as OpenAI, but don't support the more recently added `stream_options` parameter to return streaming usage. You can use `ChatOpenAI` to access these providers by disabling streaming usage like this:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "0ff40bd7", + "metadata": {}, + "outputs": [], + "source": [ + "import { ChatOpenAI } from \"@langchain/openai\";\n", + "\n", + "const llmWithoutStreamUsage = new ChatOpenAI({\n", + " temperature: 0.9,\n", + " streamUsage: false,\n", + " configuration: {\n", + " baseURL: \"https://proxy.com\",\n", + " },\n", + "});\n", + "\n", + "await llmWithoutStreamUsage.invoke(\"Hi there!\");" + ] + }, { "cell_type": "markdown", "id": "013b6300", From b8f1c120853ab5029e4cb142eee32a5f1485e0da Mon Sep 17 00:00:00 2001 From: Jacob Lee Date: Thu, 15 Aug 2024 06:41:25 -0700 Subject: [PATCH 027/344] Fix remote runnable in browser environments (Extends #6529) (#6537) * stream to streamEvents * Fetch impl fix * switch back * switch back * Fix * Comment --------- Co-authored-by: mcgear Co-authored-by: Michael Gearhardt --- langchain-core/src/runnables/remote.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/langchain-core/src/runnables/remote.ts b/langchain-core/src/runnables/remote.ts index 17df845b7472..4cae2f63d783 100644 --- a/langchain-core/src/runnables/remote.ts +++ b/langchain-core/src/runnables/remote.ts @@ -281,8 +281,13 @@ export class RemoteRunnable< private options?: RemoteRunnableOptions; + // Wrap the default fetch call due to issues with illegal invocations + // from the browser: + // https://stackoverflow.com/questions/69876859/why-does-bind-fix-failed-to-execute-fetch-on-window-illegal-invocation-err // eslint-disable-next-line @typescript-eslint/no-explicit-any - fetchImplementation: (...args: any[]) => any = fetch; + fetchImplementation: (...args: any[]) => any = (...args: any[]) => + // @ts-expect-error Broad typing to support a range of fetch implementations + fetch(...args); // eslint-disable-next-line @typescript-eslint/no-explicit-any fetchRequestOptions?: Record; From 70545676ca9632ef88138e5c64df7c433e994bbf Mon Sep 17 00:00:00 2001 From: Jacob Lee Date: Thu, 15 Aug 2024 06:55:57 -0700 Subject: [PATCH 028/344] core[patch] Release 0.2.24 (#6538) --- 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 69a1058f220e..048f0bc4a143 100644 --- a/langchain-core/package.json +++ b/langchain-core/package.json @@ -1,6 +1,6 @@ { "name": "@langchain/core", - "version": "0.2.23", + "version": "0.2.24", "description": "Core LangChain.js abstractions and schemas", "type": "module", "engines": { From d7d63c545d5bd604637f08a2ae86b71799fce144 Mon Sep 17 00:00:00 2001 From: pokutuna Date: Fri, 16 Aug 2024 17:34:13 +0900 Subject: [PATCH 029/344] google[patch]: fix: handling multibyte characters in stream for google-vertexai-web (#6502) * fix: handling multibyte characters in stream for google-vertexai-web * fix: update import path to use .js on test --- .../src/tests/utils.test.ts | 55 +++++++++++++++++++ .../src/utils/stream.ts | 4 +- 2 files changed, 57 insertions(+), 2 deletions(-) diff --git a/libs/langchain-google-common/src/tests/utils.test.ts b/libs/langchain-google-common/src/tests/utils.test.ts index 84f12dca4dd0..c0398a218c67 100644 --- a/libs/langchain-google-common/src/tests/utils.test.ts +++ b/libs/langchain-google-common/src/tests/utils.test.ts @@ -2,6 +2,7 @@ import { expect, test } from "@jest/globals"; import { z } from "zod"; import { zodToGeminiParameters } from "../utils/zod_to_gemini_parameters.js"; +import { ReadableJsonStream } from "../utils/stream.js"; test("zodToGeminiParameters can convert zod schema to gemini schema", () => { const zodSchema = z @@ -80,3 +81,57 @@ test("zodToGeminiParameters removes additional properties from arrays", () => { expect((arrayItemsSchema as any).additionalProperties).toBeUndefined(); } }); + +function toUint8Array(data: string): Uint8Array { + return new TextEncoder().encode(data); +} + +test("ReadableJsonStream can handle stream", async () => { + const data = [ + toUint8Array("["), + toUint8Array('{"i": 1}'), + toUint8Array('{"i'), + toUint8Array('": 2}'), + toUint8Array("]"), + ]; + + const source = new ReadableStream({ + start(controller) { + data.forEach((chunk) => controller.enqueue(chunk)); + controller.close(); + }, + }); + const stream = new ReadableJsonStream(source); + expect(await stream.nextChunk()).toEqual({ i: 1 }); + expect(await stream.nextChunk()).toEqual({ i: 2 }); + expect(await stream.nextChunk()).toBeNull(); + expect(stream.streamDone).toEqual(true); +}); + +test("ReadableJsonStream can handle multibyte stream", async () => { + const data = [ + toUint8Array("["), + toUint8Array('{"i": 1, "msg":"hello👋"}'), + toUint8Array('{"i": 2,'), + toUint8Array('"msg":"こん'), + new Uint8Array([0xe3]), // 1st byte of "に" + new Uint8Array([0x81, 0xab]), // 2-3rd bytes of "に" + toUint8Array("ちは"), + new Uint8Array([0xf0, 0x9f]), // first half bytes of "👋" + new Uint8Array([0x91, 0x8b]), // second half bytes of "👋" + toUint8Array('"}'), + toUint8Array("]"), + ]; + + const source = new ReadableStream({ + start(controller) { + data.forEach((chunk) => controller.enqueue(chunk)); + controller.close(); + }, + }); + const stream = new ReadableJsonStream(source); + expect(await stream.nextChunk()).toEqual({ i: 1, msg: "hello👋" }); + expect(await stream.nextChunk()).toEqual({ i: 2, msg: "こんにちは👋" }); + expect(await stream.nextChunk()).toBeNull(); + expect(stream.streamDone).toEqual(true); +}); diff --git a/libs/langchain-google-common/src/utils/stream.ts b/libs/langchain-google-common/src/utils/stream.ts index 63ba44edbd4e..226ac49dca10 100644 --- a/libs/langchain-google-common/src/utils/stream.ts +++ b/libs/langchain-google-common/src/utils/stream.ts @@ -252,7 +252,7 @@ export class ReadableJsonStream extends JsonStream { constructor(body: ReadableStream | null) { super(); - this.decoder = new TextDecoder(); + this.decoder = new TextDecoder("utf-8"); if (body) { void this.run(body); } else { @@ -266,7 +266,7 @@ export class ReadableJsonStream extends JsonStream { while (!isDone) { const { value, done } = await reader.read(); if (!done) { - const svalue = this.decoder.decode(value); + const svalue = this.decoder.decode(value, { stream: true }); this.appendBuffer(svalue); } else { isDone = done; From a41600d35dac73e2cbc7cac016c04ccd1bcd6ed0 Mon Sep 17 00:00:00 2001 From: Brace Sproul Date: Fri, 16 Aug 2024 01:37:29 -0700 Subject: [PATCH 030/344] community[patch]: Relax googleapis peer dep to * (#6542) --- libs/langchain-community/package.json | 2 +- yarn.lock | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/libs/langchain-community/package.json b/libs/langchain-community/package.json index a958154dffaf..e53903cef844 100644 --- a/libs/langchain-community/package.json +++ b/libs/langchain-community/package.json @@ -293,7 +293,7 @@ "faiss-node": "^0.5.1", "firebase-admin": "^11.9.0 || ^12.0.0", "google-auth-library": "*", - "googleapis": "^126.0.1", + "googleapis": "*", "hnswlib-node": "^3.0.0", "html-to-text": "^9.0.5", "ignore": "^5.2.0", diff --git a/yarn.lock b/yarn.lock index 232e9bc34685..3e2b408787d1 100644 --- a/yarn.lock +++ b/yarn.lock @@ -11403,7 +11403,7 @@ __metadata: faiss-node: ^0.5.1 firebase-admin: ^11.9.0 || ^12.0.0 google-auth-library: "*" - googleapis: ^126.0.1 + googleapis: "*" hnswlib-node: ^3.0.0 html-to-text: ^9.0.5 ignore: ^5.2.0 From 54fbebd34e193a06d2ae0c0a3574168cfe3420c3 Mon Sep 17 00:00:00 2001 From: Peter Lisy Date: Fri, 16 Aug 2024 01:37:54 -0700 Subject: [PATCH 031/344] Fix(aws): Use abort signal when available with SDK requests (#6540) Co-authored-by: Peter Lisy --- libs/langchain-aws/src/chat_models.ts | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/libs/langchain-aws/src/chat_models.ts b/libs/langchain-aws/src/chat_models.ts index 8de6af7712a9..ee0a4fcc6634 100644 --- a/libs/langchain-aws/src/chat_models.ts +++ b/libs/langchain-aws/src/chat_models.ts @@ -817,7 +817,9 @@ export class ChatBedrockConverse system: converseSystem, ...params, }); - const response = await this.client.send(command); + const response = await this.client.send(command, { + abortSignal: options.signal, + }); const { output, ...responseMetadata } = response; if (!output?.message) { throw new Error("No message found in Bedrock response."); @@ -855,7 +857,9 @@ export class ChatBedrockConverse system: converseSystem, ...params, }); - const response = await this.client.send(command); + const response = await this.client.send(command, { + abortSignal: options.signal, + }); if (response.stream) { for await (const chunk of response.stream) { if (chunk.contentBlockStart) { From f1b7ad74f91d6a199b0b0981be3f2fe362a67b93 Mon Sep 17 00:00:00 2001 From: choeller Date: Fri, 16 Aug 2024 11:02:52 +0200 Subject: [PATCH 032/344] =?UTF-8?q?community[patch]:=20fix:=20ElasticVecto?= =?UTF-8?q?rSearch:=20exclude=20metadata=20filters=20=E2=80=A6=20(#6536)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * community[patch]: fix: ElasticVectorSearch: exclude metadata filters not working due to syntax error in filter creation * Adds integration test --------- Co-authored-by: Christoph Höller Co-authored-by: jacoblee93 --- .../langchain-community/src/vectorstores/elasticsearch.ts | 2 +- .../src/vectorstores/tests/elasticsearch.int.test.ts | 8 ++++++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/libs/langchain-community/src/vectorstores/elasticsearch.ts b/libs/langchain-community/src/vectorstores/elasticsearch.ts index 81f480c3f1fc..14a4ee12ac03 100644 --- a/libs/langchain-community/src/vectorstores/elasticsearch.ts +++ b/libs/langchain-community/src/vectorstores/elasticsearch.ts @@ -344,7 +344,7 @@ export class ElasticVectorSearch extends VectorStore { }); } else if (condition.operator === "exclude") { must_not.push({ - terms: { + term: { [metadataField]: condition.value, }, }); diff --git a/libs/langchain-community/src/vectorstores/tests/elasticsearch.int.test.ts b/libs/langchain-community/src/vectorstores/tests/elasticsearch.int.test.ts index cc22163c7763..08ec39ac0f57 100644 --- a/libs/langchain-community/src/vectorstores/tests/elasticsearch.int.test.ts +++ b/libs/langchain-community/src/vectorstores/tests/elasticsearch.int.test.ts @@ -106,6 +106,14 @@ describe("ElasticVectorSearch", () => { ]); const results = await store.similaritySearch("*", 11); expect(results).toHaveLength(11); + const results2 = await store.similaritySearch("*", 11, [ + { + field: "a", + value: createdAt, + operator: "exclude", + }, + ]); + expect(results2).toHaveLength(1); }); test.skip("ElasticVectorSearch integration with text splitting metadata", async () => { From d7373868b9c1c7f52c747d0b47141ce4f7a92e14 Mon Sep 17 00:00:00 2001 From: Kousha Talebian Date: Fri, 16 Aug 2024 02:03:02 -0700 Subject: [PATCH 033/344] community[patch]: bugfix: Unstructured empty text (#6532) * check for empty text * add missing options for unstructured * Format --------- Co-authored-by: jacoblee93 --- .../src/document_loaders/fs/unstructured.ts | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/libs/langchain-community/src/document_loaders/fs/unstructured.ts b/libs/langchain-community/src/document_loaders/fs/unstructured.ts index 48915c89fdc5..2a9bc3271b10 100644 --- a/libs/langchain-community/src/document_loaders/fs/unstructured.ts +++ b/libs/langchain-community/src/document_loaders/fs/unstructured.ts @@ -114,6 +114,8 @@ export type UnstructuredLoaderOptions = { newAfterNChars?: number; maxCharacters?: number; extractImageBlockTypes?: string[]; + overlap?: number; + overlapAll?: boolean; }; export type UnstructuredDirectoryLoaderOptions = UnstructuredLoaderOptions & { @@ -181,6 +183,10 @@ export class UnstructuredLoader extends BaseDocumentLoader { private extractImageBlockTypes?: string[]; + private overlap?: number; + + private overlapAll?: boolean; + constructor( filepathOrBufferOptions: string | UnstructuredMemoryLoaderOptions, unstructuredOptions: UnstructuredLoaderOptions | string = {} @@ -225,6 +231,8 @@ export class UnstructuredLoader extends BaseDocumentLoader { this.newAfterNChars = options.newAfterNChars; this.maxCharacters = options.maxCharacters; this.extractImageBlockTypes = options.extractImageBlockTypes; + this.overlap = options.overlap; + this.overlapAll = options.overlapAll ?? false; } } @@ -299,6 +307,14 @@ export class UnstructuredLoader extends BaseDocumentLoader { ); } + if (this.overlap !== undefined) { + formData.append("overlap", String(this.overlap)); + } + + if (this.overlapAll === true) { + formData.append("overlap_all", "true"); + } + const headers = { "UNSTRUCTURED-API-KEY": this.apiKey ?? "", }; @@ -332,7 +348,7 @@ export class UnstructuredLoader extends BaseDocumentLoader { const documents: Document[] = []; for (const element of elements) { const { metadata, text } = element; - if (typeof text === "string") { + if (typeof text === "string" && text !== "") { documents.push( new Document({ pageContent: text, From 64226da5a578757f2806c3e0235d812a6866ff14 Mon Sep 17 00:00:00 2001 From: nick-w-nick <43578531+nick-w-nick@users.noreply.github.com> Date: Fri, 16 Aug 2024 05:39:19 -0400 Subject: [PATCH 034/344] pinecone[patch]: Fix document ID not getting set when returned from PineconeStore (#6539) * added _formatMatches privatemethod * updated similaritySearchVectorWithScore to use _formatMatches * updated maxMarginalRelevanceSearch to use _formatMatches * Add integration test --------- Co-authored-by: jacoblee93 --- .../src/tests/vectorstores.int.test.ts | 60 ++++++++++++----- libs/langchain-pinecone/src/vectorstores.ts | 66 ++++++++++++------- 2 files changed, 83 insertions(+), 43 deletions(-) diff --git a/libs/langchain-pinecone/src/tests/vectorstores.int.test.ts b/libs/langchain-pinecone/src/tests/vectorstores.int.test.ts index b4cb28c50b05..074dccae9004 100644 --- a/libs/langchain-pinecone/src/tests/vectorstores.int.test.ts +++ b/libs/langchain-pinecone/src/tests/vectorstores.int.test.ts @@ -1,6 +1,7 @@ /* eslint-disable no-process-env */ /* eslint-disable @typescript-eslint/no-non-null-assertion */ /* eslint-disable no-promise-executor-return */ +/* eslint-disable @typescript-eslint/no-explicit-any */ import { describe, expect, test } from "@jest/globals"; import { faker } from "@faker-js/faker"; import { Pinecone } from "@pinecone-database/pinecone"; @@ -9,12 +10,14 @@ import { SyntheticEmbeddings } from "@langchain/core/utils/testing"; import { Document } from "@langchain/core/documents"; import { PineconeStoreParams, PineconeStore } from "../vectorstores.js"; +const PINECONE_SLEEP_LENGTH = 40000; + function sleep(ms: number) { // eslint-disable-next-line no-promise-executor-return return new Promise((resolve) => setTimeout(resolve, ms)); } -describe.skip("PineconeStore", () => { +describe("PineconeStore", () => { let pineconeStore: PineconeStore; const testIndexName = process.env.PINECONE_INDEX!; let namespaces: string[] = []; @@ -57,22 +60,29 @@ describe.skip("PineconeStore", () => { [{ pageContent, metadata: {} }], [documentId] ); - await sleep(35000); + + await sleep(PINECONE_SLEEP_LENGTH); const results = await pineconeStore.similaritySearch(pageContent, 1); - expect(results).toEqual([new Document({ metadata: {}, pageContent })]); + expect(results).toEqual([ + new Document({ metadata: {}, pageContent, id: documentId }), + ]); await pineconeStore.addDocuments( [{ pageContent: `${pageContent} upserted`, metadata: {} }], [documentId] ); - await sleep(35000); + await sleep(PINECONE_SLEEP_LENGTH); const results2 = await pineconeStore.similaritySearch(pageContent, 1); expect(results2).toEqual([ - new Document({ metadata: {}, pageContent: `${pageContent} upserted` }), + new Document({ + metadata: {}, + pageContent: `${pageContent} upserted`, + id: documentId, + }), ]); }); @@ -83,11 +93,15 @@ describe.skip("PineconeStore", () => { { pageContent, metadata: { foo: "bar" } }, ]); - await sleep(35000); + await sleep(PINECONE_SLEEP_LENGTH); const results = await pineconeStore.similaritySearch(pageContent, 1); expect(results).toEqual([ - new Document({ metadata: { foo: "bar" }, pageContent }), + new Document({ + metadata: { foo: "bar" }, + pageContent, + id: expect.any(String) as any, + }), ]); }); @@ -100,14 +114,18 @@ describe.skip("PineconeStore", () => { { pageContent, metadata: { foo: id } }, { pageContent, metadata: { foo: "qux" } }, ]); - await sleep(35000); + await sleep(PINECONE_SLEEP_LENGTH); // If the filter wasn't working, we'd get all 3 documents back const results = await pineconeStore.similaritySearch(pageContent, 3, { foo: id, }); expect(results).toEqual([ - new Document({ metadata: { foo: id }, pageContent }), + new Document({ + metadata: { foo: id }, + pageContent, + id: expect.any(String) as any, + }), ]); }); @@ -120,7 +138,7 @@ describe.skip("PineconeStore", () => { { pageContent, metadata: { foo: id } }, { pageContent, metadata: { foo: id } }, ]); - await sleep(35000); + await sleep(PINECONE_SLEEP_LENGTH); // If the filter wasn't working, we'd get all 3 documents back const results = await pineconeStore.maxMarginalRelevanceSearch( pageContent, @@ -142,7 +160,7 @@ describe.skip("PineconeStore", () => { { pageContent, metadata: { foo: id } }, { pageContent, metadata: { foo: id } }, ]); - await sleep(35000); + await sleep(PINECONE_SLEEP_LENGTH); const results = await pineconeStore.similaritySearch(pageContent, 2, { foo: id, }); @@ -174,7 +192,7 @@ describe.skip("PineconeStore", () => { ids: [id, id2], } ); - await sleep(40000); + await sleep(PINECONE_SLEEP_LENGTH); const indexStats = await pineconeStore.pineconeIndex.describeIndexStats(); expect(indexStats.namespaces).toHaveProperty(""); expect(indexStats.namespaces?.[""].recordCount).toEqual(2); @@ -184,7 +202,7 @@ describe.skip("PineconeStore", () => { await pineconeStore.delete({ deleteAll: true, }); - await sleep(40000); + await sleep(PINECONE_SLEEP_LENGTH); const indexStats2 = await pineconeStore.pineconeIndex.describeIndexStats(); expect(indexStats2.namespaces).not.toHaveProperty(""); // The new total records should be less than the previous total records @@ -209,7 +227,7 @@ describe.skip("PineconeStore", () => { namespace: namespaces[1], } ); - await sleep(35000); + await sleep(PINECONE_SLEEP_LENGTH); const results = await pineconeStore.similaritySearch(pageContent, 1, { namespace: namespaces[0], }); @@ -234,22 +252,28 @@ describe.skip("PineconeStore", () => { }); await store.addDocuments([{ pageContent, metadata: {} }], [documentId]); - await sleep(35000); + await sleep(PINECONE_SLEEP_LENGTH); const results = await store.similaritySearch(pageContent, 1); - expect(results).toEqual([new Document({ metadata: {}, pageContent })]); + expect(results).toEqual([ + new Document({ metadata: {}, pageContent, id: documentId }), + ]); await store.addDocuments( [{ pageContent: `${pageContent} upserted`, metadata: {} }], [documentId] ); - await sleep(35000); + await sleep(PINECONE_SLEEP_LENGTH); const results2 = await store.similaritySearch(pageContent, 1); expect(results2).toEqual([ - new Document({ metadata: {}, pageContent: `${pageContent} upserted` }), + new Document({ + metadata: {}, + pageContent: `${pageContent} upserted`, + id: documentId, + }), ]); }); }); diff --git a/libs/langchain-pinecone/src/vectorstores.ts b/libs/langchain-pinecone/src/vectorstores.ts index 3a99dbceb057..2228d2456744 100644 --- a/libs/langchain-pinecone/src/vectorstores.ts +++ b/libs/langchain-pinecone/src/vectorstores.ts @@ -5,6 +5,7 @@ import { RecordMetadata, PineconeRecord, Index as PineconeIndex, + ScoredPineconeRecord, } from "@pinecone-database/pinecone"; import type { EmbeddingsInterface } from "@langchain/core/embeddings"; @@ -401,6 +402,40 @@ export class PineconeStore extends VectorStore { return results; } + /** + * Format the matching results from the Pinecone query. + * @param matches Matching results from the Pinecone query. + * @returns An array of arrays, where each inner array contains a document and its score. + */ + private _formatMatches( + matches: ScoredPineconeRecord[] = [] + ): [Document, number][] { + const documentsWithScores: [Document, number][] = []; + + for (const record of matches) { + const { + id, + score, + metadata: { [this.textKey]: pageContent, ...metadata } = { + [this.textKey]: "", + }, + } = record; + + if (score) { + documentsWithScores.push([ + new Document({ + id, + pageContent: pageContent.toString(), + metadata, + }), + score, + ]); + } + } + + return documentsWithScores; + } + /** * Method that performs a similarity search in the Pinecone database and * returns the results along with their scores. @@ -414,20 +449,10 @@ export class PineconeStore extends VectorStore { k: number, filter?: PineconeMetadata ): Promise<[Document, number][]> { - const results = await this._runPineconeQuery(query, k, filter); - const result: [Document, number][] = []; - - if (results.matches) { - for (const res of results.matches) { - const { [this.textKey]: pageContent, ...metadata } = (res.metadata ?? - {}) as PineconeMetadata; - if (res.score) { - result.push([new Document({ metadata, pageContent }), res.score]); - } - } - } + const { matches = [] } = await this._runPineconeQuery(query, k, filter); + const records = this._formatMatches(matches); - return result; + return records; } /** @@ -457,7 +482,7 @@ export class PineconeStore extends VectorStore { { includeValues: true } ); - const matches = results?.matches ?? []; + const { matches = [] } = results; const embeddingList = matches.map((match) => match.values); const mmrIndexes = maximalMarginalRelevance( @@ -468,17 +493,8 @@ export class PineconeStore extends VectorStore { ); const topMmrMatches = mmrIndexes.map((idx) => matches[idx]); - - const finalResult: Document[] = []; - for (const res of topMmrMatches) { - const { [this.textKey]: pageContent, ...metadata } = (res.metadata ?? - {}) as PineconeMetadata; - if (res.score) { - finalResult.push(new Document({ metadata, pageContent })); - } - } - - return finalResult; + const records = this._formatMatches(topMmrMatches); + return records.map(([doc, _score]) => doc); } /** From 3eae00260982cf90c5dbc5e3fa08b2a194284efd Mon Sep 17 00:00:00 2001 From: Jacob Lee Date: Fri, 16 Aug 2024 02:49:10 -0700 Subject: [PATCH 035/344] community[patch]: Release 0.2.28 (#6543) --- libs/langchain-community/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libs/langchain-community/package.json b/libs/langchain-community/package.json index e53903cef844..87b03e431e17 100644 --- a/libs/langchain-community/package.json +++ b/libs/langchain-community/package.json @@ -1,6 +1,6 @@ { "name": "@langchain/community", - "version": "0.2.27", + "version": "0.2.28", "description": "Third-party integrations for LangChain.js", "type": "module", "engines": { From 3c325258d54fde829c190ce5781aeaf064512155 Mon Sep 17 00:00:00 2001 From: Jacob Lee Date: Fri, 16 Aug 2024 02:53:18 -0700 Subject: [PATCH 036/344] aws[patch]: Release 0.0.10 (#6544) --- libs/langchain-aws/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libs/langchain-aws/package.json b/libs/langchain-aws/package.json index a82182b6f3a4..dcca5722d2c6 100644 --- a/libs/langchain-aws/package.json +++ b/libs/langchain-aws/package.json @@ -1,6 +1,6 @@ { "name": "@langchain/aws", - "version": "0.0.9", + "version": "0.0.10", "description": "LangChain AWS integration", "type": "module", "engines": { From bbf506285532661fa4c17b900ea00660c6711662 Mon Sep 17 00:00:00 2001 From: Jacob Lee Date: Fri, 16 Aug 2024 02:56:42 -0700 Subject: [PATCH 037/344] pinecone[patch]: Release 0.0.9 (#6545) --- libs/langchain-pinecone/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libs/langchain-pinecone/package.json b/libs/langchain-pinecone/package.json index a6590d755e13..6fc4037b6e6f 100644 --- a/libs/langchain-pinecone/package.json +++ b/libs/langchain-pinecone/package.json @@ -1,6 +1,6 @@ { "name": "@langchain/pinecone", - "version": "0.0.8", + "version": "0.0.9", "description": "LangChain integration for Pinecone's vector database", "type": "module", "engines": { From 5e96b858415e8bc41d7594c0e2ddfa13c63de449 Mon Sep 17 00:00:00 2001 From: Tri Rahmat Gunadi Date: Fri, 16 Aug 2024 16:58:04 +0700 Subject: [PATCH 038/344] google-genai[minor]: Add support for json output (#6519) * support json output for google-genai * Apply suggestions from code review * Add test --------- Co-authored-by: juji Co-authored-by: Brace Sproul Co-authored-by: jacoblee93 --- .../langchain-google-genai/src/chat_models.ts | 10 ++++++++++ .../src/tests/chat_models.int.test.ts | 19 +++++++++++++++++++ 2 files changed, 29 insertions(+) diff --git a/libs/langchain-google-genai/src/chat_models.ts b/libs/langchain-google-genai/src/chat_models.ts index ddd407df6031..197febd63bbb 100644 --- a/libs/langchain-google-genai/src/chat_models.ts +++ b/libs/langchain-google-genai/src/chat_models.ts @@ -75,6 +75,8 @@ export interface GoogleGenerativeAIChatInput extends BaseChatModelParams, Pick { /** + * @deprecated Use "model" instead. + * * Model Name to use * * Alias for `model` @@ -167,6 +169,13 @@ export interface GoogleGenerativeAIChatInput /** Whether to stream the results or not */ streaming?: boolean; + + /** + * Whether or not to force the model to respond with JSON. + * Available for `gemini-1.5` models and later. + * @default false + */ + json?: boolean; } /** @@ -321,6 +330,7 @@ export class ChatGoogleGenerativeAI temperature: this.temperature, topP: this.topP, topK: this.topK, + ...(fields?.json ? { responseMimeType: "application/json" } : {}), }, }, { diff --git a/libs/langchain-google-genai/src/tests/chat_models.int.test.ts b/libs/langchain-google-genai/src/tests/chat_models.int.test.ts index 4e4791495759..ddf5aee6bfc7 100644 --- a/libs/langchain-google-genai/src/tests/chat_models.int.test.ts +++ b/libs/langchain-google-genai/src/tests/chat_models.int.test.ts @@ -519,3 +519,22 @@ test("Invoke token count usage_metadata", async () => { res.usage_metadata.input_tokens + res.usage_metadata.output_tokens ); }); + +test("Invoke with JSON mode", async () => { + const model = new ChatGoogleGenerativeAI({ + model: "gemini-1.5-flash", + temperature: 0, + maxOutputTokens: 10, + json: true, + }); + const res = await model.invoke("Why is the sky blue? Be concise."); + expect(res?.usage_metadata).toBeDefined(); + if (!res?.usage_metadata) { + return; + } + expect(res.usage_metadata.input_tokens).toBeGreaterThan(1); + expect(res.usage_metadata.output_tokens).toBeGreaterThan(1); + expect(res.usage_metadata.total_tokens).toBe( + res.usage_metadata.input_tokens + res.usage_metadata.output_tokens + ); +}); From f842109fdc6013e876138a9d24c349792f0d3b29 Mon Sep 17 00:00:00 2001 From: Jacob Lee Date: Fri, 16 Aug 2024 03:04:00 -0700 Subject: [PATCH 039/344] Bump Google package versions (#6546) --- libs/langchain-google-common/package.json | 2 +- libs/langchain-google-gauth/package.json | 4 +-- .../package.json | 4 +-- libs/langchain-google-vertexai/package.json | 4 +-- libs/langchain-google-webauth/package.json | 4 +-- yarn.lock | 26 +++++++++++++------ 6 files changed, 27 insertions(+), 17 deletions(-) diff --git a/libs/langchain-google-common/package.json b/libs/langchain-google-common/package.json index 567fe51b5efb..fca557792abf 100644 --- a/libs/langchain-google-common/package.json +++ b/libs/langchain-google-common/package.json @@ -1,6 +1,6 @@ { "name": "@langchain/google-common", - "version": "0.0.25", + "version": "0.0.26", "description": "Core types and classes for Google services.", "type": "module", "engines": { diff --git a/libs/langchain-google-gauth/package.json b/libs/langchain-google-gauth/package.json index da8acef40b88..3ad08757808b 100644 --- a/libs/langchain-google-gauth/package.json +++ b/libs/langchain-google-gauth/package.json @@ -1,6 +1,6 @@ { "name": "@langchain/google-gauth", - "version": "0.0.25", + "version": "0.0.26", "description": "Google auth based authentication support for Google services", "type": "module", "engines": { @@ -36,7 +36,7 @@ "license": "MIT", "dependencies": { "@langchain/core": ">=0.2.21 <0.3.0", - "@langchain/google-common": "~0.0.25", + "@langchain/google-common": "~0.0.26", "google-auth-library": "^8.9.0" }, "devDependencies": { diff --git a/libs/langchain-google-vertexai-web/package.json b/libs/langchain-google-vertexai-web/package.json index b9000bfbc7ca..d5b5d3b3f29f 100644 --- a/libs/langchain-google-vertexai-web/package.json +++ b/libs/langchain-google-vertexai-web/package.json @@ -1,6 +1,6 @@ { "name": "@langchain/google-vertexai-web", - "version": "0.0.25", + "version": "0.0.26", "description": "LangChain.js support for Google Vertex AI Web", "type": "module", "engines": { @@ -41,7 +41,7 @@ "license": "MIT", "dependencies": { "@langchain/core": ">=0.2.21 <0.3.0", - "@langchain/google-webauth": "~0.0.25" + "@langchain/google-webauth": "~0.0.26" }, "devDependencies": { "@jest/globals": "^29.5.0", diff --git a/libs/langchain-google-vertexai/package.json b/libs/langchain-google-vertexai/package.json index e1ac98e021d4..5b5631332d53 100644 --- a/libs/langchain-google-vertexai/package.json +++ b/libs/langchain-google-vertexai/package.json @@ -1,6 +1,6 @@ { "name": "@langchain/google-vertexai", - "version": "0.0.25", + "version": "0.0.26", "description": "LangChain.js support for Google Vertex AI", "type": "module", "engines": { @@ -44,7 +44,7 @@ "license": "MIT", "dependencies": { "@langchain/core": ">=0.2.21 <0.3.0", - "@langchain/google-gauth": "~0.0.25" + "@langchain/google-gauth": "~0.0.26" }, "devDependencies": { "@jest/globals": "^29.5.0", diff --git a/libs/langchain-google-webauth/package.json b/libs/langchain-google-webauth/package.json index 316de548ce44..775cd60184c8 100644 --- a/libs/langchain-google-webauth/package.json +++ b/libs/langchain-google-webauth/package.json @@ -1,6 +1,6 @@ { "name": "@langchain/google-webauth", - "version": "0.0.25", + "version": "0.0.26", "description": "Web-based authentication support for Google services", "type": "module", "engines": { @@ -41,7 +41,7 @@ "license": "MIT", "dependencies": { "@langchain/core": ">=0.2.21 <0.3.0", - "@langchain/google-common": "~0.0.25", + "@langchain/google-common": "~0.0.26", "web-auth-library": "^1.0.3" }, "devDependencies": { diff --git a/yarn.lock b/yarn.lock index 3e2b408787d1..cbb711aa6478 100644 --- a/yarn.lock +++ b/yarn.lock @@ -11760,7 +11760,7 @@ __metadata: languageName: unknown linkType: soft -"@langchain/google-common@workspace:*, @langchain/google-common@workspace:libs/langchain-google-common, @langchain/google-common@~0.0, @langchain/google-common@~0.0.25": +"@langchain/google-common@workspace:*, @langchain/google-common@workspace:libs/langchain-google-common, @langchain/google-common@~0.0, @langchain/google-common@~0.0.26": version: 0.0.0-use.local resolution: "@langchain/google-common@workspace:libs/langchain-google-common" dependencies: @@ -11793,13 +11793,13 @@ __metadata: languageName: unknown linkType: soft -"@langchain/google-gauth@workspace:libs/langchain-google-gauth, @langchain/google-gauth@~0.0.25": +"@langchain/google-gauth@workspace:libs/langchain-google-gauth, @langchain/google-gauth@~0.0.25, @langchain/google-gauth@~0.0.26": version: 0.0.0-use.local resolution: "@langchain/google-gauth@workspace:libs/langchain-google-gauth" dependencies: "@jest/globals": ^29.5.0 "@langchain/core": ">=0.2.21 <0.3.0" - "@langchain/google-common": ~0.0.25 + "@langchain/google-common": ~0.0.26 "@langchain/scripts": ~0.0.20 "@swc/core": ^1.3.90 "@swc/jest": ^0.2.29 @@ -11867,7 +11867,7 @@ __metadata: dependencies: "@jest/globals": ^29.5.0 "@langchain/core": ">=0.2.21 <0.3.0" - "@langchain/google-webauth": ~0.0.25 + "@langchain/google-webauth": ~0.0.26 "@langchain/scripts": ~0.0.20 "@swc/core": ^1.3.90 "@swc/jest": ^0.2.29 @@ -11893,14 +11893,24 @@ __metadata: languageName: unknown linkType: soft -"@langchain/google-vertexai@^0.0.25, @langchain/google-vertexai@workspace:*, @langchain/google-vertexai@workspace:libs/langchain-google-vertexai": +"@langchain/google-vertexai@npm:^0.0.25": + version: 0.0.25 + resolution: "@langchain/google-vertexai@npm:0.0.25" + dependencies: + "@langchain/core": ">=0.2.21 <0.3.0" + "@langchain/google-gauth": ~0.0.25 + checksum: 8ffc55b8d7a797277780cdab1851db606889e1063420c6a01f95b2c9b98f8a5708250c55bb25a8fb74b2052b4b83c8a63283f9a4c0ba769f86bacfcab22b3f50 + languageName: node + linkType: hard + +"@langchain/google-vertexai@workspace:*, @langchain/google-vertexai@workspace:libs/langchain-google-vertexai": version: 0.0.0-use.local resolution: "@langchain/google-vertexai@workspace:libs/langchain-google-vertexai" dependencies: "@jest/globals": ^29.5.0 "@langchain/core": ">=0.2.21 <0.3.0" "@langchain/google-common": ~0.0 - "@langchain/google-gauth": ~0.0.25 + "@langchain/google-gauth": ~0.0.26 "@langchain/scripts": ~0.0.20 "@langchain/standard-tests": 0.0.0 "@swc/core": ^1.3.90 @@ -11927,13 +11937,13 @@ __metadata: languageName: unknown linkType: soft -"@langchain/google-webauth@workspace:libs/langchain-google-webauth, @langchain/google-webauth@~0.0.25": +"@langchain/google-webauth@workspace:libs/langchain-google-webauth, @langchain/google-webauth@~0.0.26": version: 0.0.0-use.local resolution: "@langchain/google-webauth@workspace:libs/langchain-google-webauth" dependencies: "@jest/globals": ^29.5.0 "@langchain/core": ">=0.2.21 <0.3.0" - "@langchain/google-common": ~0.0.25 + "@langchain/google-common": ~0.0.26 "@langchain/scripts": ~0.0.20 "@swc/core": ^1.3.90 "@swc/jest": ^0.2.29 From a4cf3332bf093b26b5912cae0c51984c305e1f46 Mon Sep 17 00:00:00 2001 From: Jacob Lee Date: Fri, 16 Aug 2024 03:06:39 -0700 Subject: [PATCH 040/344] google-genai[patch]: Release 0.0.26 (#6547) --- libs/langchain-google-genai/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libs/langchain-google-genai/package.json b/libs/langchain-google-genai/package.json index 64cf3fda7e7b..ecafca892bd1 100644 --- a/libs/langchain-google-genai/package.json +++ b/libs/langchain-google-genai/package.json @@ -1,6 +1,6 @@ { "name": "@langchain/google-genai", - "version": "0.0.25", + "version": "0.0.26", "description": "Google Generative AI integration for LangChain.js", "type": "module", "engines": { From f730d5e8e8f0a50bba8e65d3d9385a69ad8510fc Mon Sep 17 00:00:00 2001 From: Jacob Lee Date: Fri, 16 Aug 2024 03:08:53 -0700 Subject: [PATCH 041/344] Update lock (#6548) --- yarn.lock | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/yarn.lock b/yarn.lock index cbb711aa6478..cba13982d844 100644 --- a/yarn.lock +++ b/yarn.lock @@ -11826,7 +11826,18 @@ __metadata: languageName: unknown linkType: soft -"@langchain/google-genai@^0.0.25, @langchain/google-genai@workspace:*, @langchain/google-genai@workspace:libs/langchain-google-genai": +"@langchain/google-genai@npm:^0.0.25": + version: 0.0.25 + resolution: "@langchain/google-genai@npm:0.0.25" + dependencies: + "@google/generative-ai": ^0.7.0 + "@langchain/core": ">=0.2.21 <0.3.0" + zod-to-json-schema: ^3.22.4 + checksum: 0119986f3f2c128c6eabf93ddc818fcc53773e91cc58154e416cbe9cefe15586e24776f21ca08d3a029701df1a1b8c45ce2c53c5d552a6df4566e08604e5bca9 + languageName: node + linkType: hard + +"@langchain/google-genai@workspace:*, @langchain/google-genai@workspace:libs/langchain-google-genai": version: 0.0.0-use.local resolution: "@langchain/google-genai@workspace:libs/langchain-google-genai" dependencies: From bdb0771f5a8157affba26495a55a21b87af59e12 Mon Sep 17 00:00:00 2001 From: Jacob Lee Date: Fri, 16 Aug 2024 04:37:27 -0700 Subject: [PATCH 042/344] Bump universal chat model deps (#6550) --- langchain/package.json | 4 ++-- yarn.lock | 31 +++++-------------------------- 2 files changed, 7 insertions(+), 28 deletions(-) diff --git a/langchain/package.json b/langchain/package.json index bbf93f6eab95..40ffadfabce1 100644 --- a/langchain/package.json +++ b/langchain/package.json @@ -616,8 +616,8 @@ "@langchain/anthropic": "^0.2.8", "@langchain/aws": "^0.0.5", "@langchain/cohere": "^0.2.1", - "@langchain/google-genai": "^0.0.25", - "@langchain/google-vertexai": "^0.0.25", + "@langchain/google-genai": "^0.0.26", + "@langchain/google-vertexai": "^0.0.26", "@langchain/groq": "^0.0.15", "@langchain/mistralai": "^0.0.26", "@langchain/ollama": "^0.0.2", diff --git a/yarn.lock b/yarn.lock index cba13982d844..6e4eb507839b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -11793,7 +11793,7 @@ __metadata: languageName: unknown linkType: soft -"@langchain/google-gauth@workspace:libs/langchain-google-gauth, @langchain/google-gauth@~0.0.25, @langchain/google-gauth@~0.0.26": +"@langchain/google-gauth@workspace:libs/langchain-google-gauth, @langchain/google-gauth@~0.0.26": version: 0.0.0-use.local resolution: "@langchain/google-gauth@workspace:libs/langchain-google-gauth" dependencies: @@ -11826,18 +11826,7 @@ __metadata: languageName: unknown linkType: soft -"@langchain/google-genai@npm:^0.0.25": - version: 0.0.25 - resolution: "@langchain/google-genai@npm:0.0.25" - dependencies: - "@google/generative-ai": ^0.7.0 - "@langchain/core": ">=0.2.21 <0.3.0" - zod-to-json-schema: ^3.22.4 - checksum: 0119986f3f2c128c6eabf93ddc818fcc53773e91cc58154e416cbe9cefe15586e24776f21ca08d3a029701df1a1b8c45ce2c53c5d552a6df4566e08604e5bca9 - languageName: node - linkType: hard - -"@langchain/google-genai@workspace:*, @langchain/google-genai@workspace:libs/langchain-google-genai": +"@langchain/google-genai@^0.0.26, @langchain/google-genai@workspace:*, @langchain/google-genai@workspace:libs/langchain-google-genai": version: 0.0.0-use.local resolution: "@langchain/google-genai@workspace:libs/langchain-google-genai" dependencies: @@ -11904,17 +11893,7 @@ __metadata: languageName: unknown linkType: soft -"@langchain/google-vertexai@npm:^0.0.25": - version: 0.0.25 - resolution: "@langchain/google-vertexai@npm:0.0.25" - dependencies: - "@langchain/core": ">=0.2.21 <0.3.0" - "@langchain/google-gauth": ~0.0.25 - checksum: 8ffc55b8d7a797277780cdab1851db606889e1063420c6a01f95b2c9b98f8a5708250c55bb25a8fb74b2052b4b83c8a63283f9a4c0ba769f86bacfcab22b3f50 - languageName: node - linkType: hard - -"@langchain/google-vertexai@workspace:*, @langchain/google-vertexai@workspace:libs/langchain-google-vertexai": +"@langchain/google-vertexai@^0.0.26, @langchain/google-vertexai@workspace:*, @langchain/google-vertexai@workspace:libs/langchain-google-vertexai": version: 0.0.0-use.local resolution: "@langchain/google-vertexai@workspace:libs/langchain-google-vertexai" dependencies: @@ -32070,8 +32049,8 @@ __metadata: "@langchain/aws": ^0.0.5 "@langchain/cohere": ^0.2.1 "@langchain/core": ">=0.2.21 <0.3.0" - "@langchain/google-genai": ^0.0.25 - "@langchain/google-vertexai": ^0.0.25 + "@langchain/google-genai": ^0.0.26 + "@langchain/google-vertexai": ^0.0.26 "@langchain/groq": ^0.0.15 "@langchain/mistralai": ^0.0.26 "@langchain/ollama": ^0.0.2 From a52630f6c733dd0014543670c645aa635e3e1ada Mon Sep 17 00:00:00 2001 From: Dominik Kundel Date: Fri, 16 Aug 2024 14:04:02 +0200 Subject: [PATCH 043/344] core[patch]: Pass input to invocation for JSON schema tools (#6549) * core[patch]: Pass input to invocation for JSON schema tools * Lint * Use type guard instead of instanceof --------- Co-authored-by: jacoblee93 --- langchain-core/src/tools/index.ts | 8 ++++++-- langchain-core/src/tools/tests/tools.test.ts | 14 ++++++++++++-- 2 files changed, 18 insertions(+), 4 deletions(-) diff --git a/langchain-core/src/tools/index.ts b/langchain-core/src/tools/index.ts index 3a73a0b53dde..a2000a24b21d 100644 --- a/langchain-core/src/tools/index.ts +++ b/langchain-core/src/tools/index.ts @@ -430,7 +430,7 @@ export class DynamicStructuredTool< this.func = fields.func; this.returnDirect = fields.returnDirect ?? this.returnDirect; this.schema = ( - isZodSchema(fields.schema) ? fields.schema : z.object({}) + isZodSchema(fields.schema) ? fields.schema : z.object({}).passthrough() ) as T extends ZodObjectAny ? T : ZodObjectAny; } @@ -557,7 +557,11 @@ export function tool< | DynamicStructuredTool | DynamicTool { // If the schema is not provided, or it's a string schema, create a DynamicTool - if (!fields.schema || !("shape" in fields.schema) || !fields.schema.shape) { + if ( + !fields.schema || + (isZodSchema(fields.schema) && + (!("shape" in fields.schema) || !fields.schema.shape)) + ) { return new DynamicTool({ ...fields, description: diff --git a/langchain-core/src/tools/tests/tools.test.ts b/langchain-core/src/tools/tests/tools.test.ts index 4c38800b3489..2988eb64d9cf 100644 --- a/langchain-core/src/tools/tests/tools.test.ts +++ b/langchain-core/src/tools/tests/tools.test.ts @@ -128,7 +128,11 @@ test("Tool declared with JSON schema", async () => { required: ["location"], }; const weatherTool = tool( - (_) => { + (input) => { + // even without validation expect input to be passed + expect(input).toEqual({ + somethingSilly: true, + }); return "Sunny"; }, { @@ -136,15 +140,21 @@ test("Tool declared with JSON schema", async () => { schema: weatherSchema, } ); + expect(weatherTool).toBeInstanceOf(DynamicStructuredTool); const weatherTool2 = new DynamicStructuredTool({ name: "weather", description: "get the weather", - func: async (_) => { + func: async (input) => { + // even without validation expect input to be passed + expect(input).toEqual({ + somethingSilly: true, + }); return "Sunny"; }, schema: weatherSchema, }); + // No validation on JSON schema tools await weatherTool.invoke({ somethingSilly: true, From 126349e430f61747e7ddf3cc4e0bd60f088145b6 Mon Sep 17 00:00:00 2001 From: Jacob Lee Date: Fri, 16 Aug 2024 05:11:37 -0700 Subject: [PATCH 044/344] core[patch]: Release 0.2.25 (#6551) --- 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 048f0bc4a143..334ef6ee3840 100644 --- a/langchain-core/package.json +++ b/langchain-core/package.json @@ -1,6 +1,6 @@ { "name": "@langchain/core", - "version": "0.2.24", + "version": "0.2.25", "description": "Core LangChain.js abstractions and schemas", "type": "module", "engines": { From 7edbf71c2a1396454ba65dae41302f45c58caa17 Mon Sep 17 00:00:00 2001 From: Brace Sproul Date: Fri, 16 Aug 2024 10:28:41 -0700 Subject: [PATCH 045/344] scripts[minor]: Add notebook validation script (#6541) * scripts[minor]: Add notebook validation script * trigger ntbk change * cr * format and lint * add lg=ogs * verify file exists * fixed * add -- to script * release 0.0.21 * bump all langchain scripts versions * Apply suggestions from code review * merge main --- .github/workflows/validate_new_notebooks.yml | 5 +- docs/core_docs/package.json | 2 +- examples/package.json | 2 +- langchain-core/package.json | 2 +- langchain/package.json | 2 +- .../template/package.json | 2 +- libs/langchain-anthropic/package.json | 2 +- libs/langchain-aws/package.json | 2 +- .../package.json | 2 +- libs/langchain-azure-openai/package.json | 2 +- libs/langchain-baidu-qianfan/package.json | 2 +- libs/langchain-cloudflare/package.json | 2 +- libs/langchain-cohere/package.json | 2 +- libs/langchain-community/package.json | 2 +- libs/langchain-exa/package.json | 2 +- libs/langchain-google-common/package.json | 2 +- libs/langchain-google-gauth/package.json | 2 +- libs/langchain-google-genai/package.json | 2 +- .../package.json | 2 +- libs/langchain-google-vertexai/package.json | 2 +- libs/langchain-google-webauth/package.json | 2 +- libs/langchain-groq/package.json | 2 +- libs/langchain-mistralai/package.json | 2 +- libs/langchain-mixedbread-ai/package.json | 2 +- libs/langchain-mongodb/package.json | 2 +- libs/langchain-nomic/package.json | 2 +- libs/langchain-ollama/package.json | 2 +- libs/langchain-openai/package.json | 2 +- libs/langchain-pinecone/package.json | 2 +- libs/langchain-qdrant/package.json | 2 +- libs/langchain-redis/package.json | 2 +- .../bin/validate_notebook.js | 1 + libs/langchain-scripts/package.json | 5 +- .../src}/validate_notebook.ts | 15 ++--- libs/langchain-textsplitters/package.json | 2 +- libs/langchain-weaviate/package.json | 2 +- libs/langchain-yandex/package.json | 2 +- package.json | 2 +- yarn.lock | 65 ++++++++++--------- 39 files changed, 81 insertions(+), 78 deletions(-) create mode 100755 libs/langchain-scripts/bin/validate_notebook.js rename {docs/core_docs/scripts => libs/langchain-scripts/src}/validate_notebook.ts (92%) diff --git a/.github/workflows/validate_new_notebooks.yml b/.github/workflows/validate_new_notebooks.yml index bb6bffebc062..aa0911d469b1 100644 --- a/.github/workflows/validate_new_notebooks.yml +++ b/.github/workflows/validate_new_notebooks.yml @@ -51,8 +51,9 @@ jobs: notebooks=$(echo '${{ steps.changed-files.outputs.all_changed_files }}' | tr ' ' '\n' | grep '^docs/core_docs/.*\.ipynb$' || true) if [ -n "$notebooks" ]; then for notebook in $notebooks; do - yarn notebook:validate "$notebook" + absolute_path="$GITHUB_WORKSPACE/$notebook" + yarn notebook:validate "$absolute_path" done else echo "No notebooks in docs/core_docs to validate." - fi \ No newline at end of file + fi diff --git a/docs/core_docs/package.json b/docs/core_docs/package.json index e71b7784e37d..9a74b1014673 100644 --- a/docs/core_docs/package.json +++ b/docs/core_docs/package.json @@ -27,7 +27,7 @@ "broken-links": "node ./scripts/check-broken-links.js", "check:broken-links": "yarn quarto && yarn broken-links", "check:broken-links:ci": "yarn quarto:vercel && yarn broken-links", - "validate": "tsx --experimental-wasm-modules -r dotenv/config ./scripts/validate_notebook.ts" + "validate": "yarn notebook_validate" }, "dependencies": { "@docusaurus/core": "2.4.3", diff --git a/examples/package.json b/examples/package.json index 0e0d8df8b6ed..1887b730af06 100644 --- a/examples/package.json +++ b/examples/package.json @@ -58,7 +58,7 @@ "@langchain/pinecone": "workspace:*", "@langchain/qdrant": "workspace:*", "@langchain/redis": "workspace:*", - "@langchain/scripts": "~0.0.20", + "@langchain/scripts": "^0.0.21", "@langchain/textsplitters": "workspace:*", "@langchain/weaviate": "workspace:*", "@langchain/yandex": "workspace:*", diff --git a/langchain-core/package.json b/langchain-core/package.json index 334ef6ee3840..1fcb7c21d297 100644 --- a/langchain-core/package.json +++ b/langchain-core/package.json @@ -55,7 +55,7 @@ }, "devDependencies": { "@jest/globals": "^29.5.0", - "@langchain/scripts": "~0.0.20", + "@langchain/scripts": "^0.0.21", "@swc/core": "^1.3.90", "@swc/jest": "^0.2.29", "@types/decamelize": "^1.2.0", diff --git a/langchain/package.json b/langchain/package.json index 40ffadfabce1..8c5d13a9b751 100644 --- a/langchain/package.json +++ b/langchain/package.json @@ -621,7 +621,7 @@ "@langchain/groq": "^0.0.15", "@langchain/mistralai": "^0.0.26", "@langchain/ollama": "^0.0.2", - "@langchain/scripts": "~0.0.20", + "@langchain/scripts": "^0.0.21", "@mendable/firecrawl-js": "^0.0.13", "@notionhq/client": "^2.2.10", "@pinecone-database/pinecone": "^1.1.0", diff --git a/libs/create-langchain-integration/template/package.json b/libs/create-langchain-integration/template/package.json index e5598ae4c7da..326580217a7b 100644 --- a/libs/create-langchain-integration/template/package.json +++ b/libs/create-langchain-integration/template/package.json @@ -45,7 +45,7 @@ "@jest/globals": "^29.5.0", "@swc/core": "^1.3.90", "@swc/jest": "^0.2.29", - "@langchain/scripts": "~0.0.20", + "@langchain/scripts": "^0.0.21", "@tsconfig/recommended": "^1.0.3", "@typescript-eslint/eslint-plugin": "^6.12.0", "@typescript-eslint/parser": "^6.12.0", diff --git a/libs/langchain-anthropic/package.json b/libs/langchain-anthropic/package.json index 314dc51b5b7d..ea843eab9532 100644 --- a/libs/langchain-anthropic/package.json +++ b/libs/langchain-anthropic/package.json @@ -43,7 +43,7 @@ }, "devDependencies": { "@jest/globals": "^29.5.0", - "@langchain/scripts": "~0.0.20", + "@langchain/scripts": "^0.0.21", "@langchain/standard-tests": "0.0.0", "@swc/core": "^1.3.90", "@swc/jest": "^0.2.29", diff --git a/libs/langchain-aws/package.json b/libs/langchain-aws/package.json index dcca5722d2c6..48e4d63696bb 100644 --- a/libs/langchain-aws/package.json +++ b/libs/langchain-aws/package.json @@ -50,7 +50,7 @@ "devDependencies": { "@aws-sdk/types": "^3.609.0", "@jest/globals": "^29.5.0", - "@langchain/scripts": "~0.0.20", + "@langchain/scripts": "^0.0.21", "@langchain/standard-tests": "0.0.0", "@smithy/types": "^3.2.0", "@swc/core": "^1.3.90", diff --git a/libs/langchain-azure-dynamic-sessions/package.json b/libs/langchain-azure-dynamic-sessions/package.json index 0fd02dfb8e8a..b6504cc0be2a 100644 --- a/libs/langchain-azure-dynamic-sessions/package.json +++ b/libs/langchain-azure-dynamic-sessions/package.json @@ -44,7 +44,7 @@ }, "devDependencies": { "@jest/globals": "^29.5.0", - "@langchain/scripts": "~0.0.20", + "@langchain/scripts": "^0.0.21", "@swc/core": "^1.3.90", "@swc/jest": "^0.2.29", "@tsconfig/recommended": "^1.0.3", diff --git a/libs/langchain-azure-openai/package.json b/libs/langchain-azure-openai/package.json index c4c1157ade8c..a386398a2fc9 100644 --- a/libs/langchain-azure-openai/package.json +++ b/libs/langchain-azure-openai/package.json @@ -41,7 +41,7 @@ "devDependencies": { "@azure/identity": "^4.2.1", "@jest/globals": "^29.5.0", - "@langchain/scripts": "~0.0.20", + "@langchain/scripts": "^0.0.21", "@langchain/standard-tests": "0.0.0", "@swc/core": "^1.3.90", "@swc/jest": "^0.2.29", diff --git a/libs/langchain-baidu-qianfan/package.json b/libs/langchain-baidu-qianfan/package.json index e268a49ba227..ae90a64fdf2e 100644 --- a/libs/langchain-baidu-qianfan/package.json +++ b/libs/langchain-baidu-qianfan/package.json @@ -40,7 +40,7 @@ "devDependencies": { "@jest/globals": "^29.5.0", "@langchain/openai": "~0.1.0", - "@langchain/scripts": "~0.0.20", + "@langchain/scripts": "^0.0.21", "@swc/core": "^1.3.90", "@swc/jest": "^0.2.29", "@tsconfig/recommended": "^1.0.3", diff --git a/libs/langchain-cloudflare/package.json b/libs/langchain-cloudflare/package.json index 249312f78c36..af14fba26d76 100644 --- a/libs/langchain-cloudflare/package.json +++ b/libs/langchain-cloudflare/package.json @@ -43,7 +43,7 @@ "@cloudflare/workers-types": "^4.20231218.0", "@jest/globals": "^29.5.0", "@langchain/langgraph": "~0.0.31", - "@langchain/scripts": "~0.0.20", + "@langchain/scripts": "^0.0.21", "@langchain/standard-tests": "0.0.0", "@swc/core": "^1.3.90", "@swc/jest": "^0.2.29", diff --git a/libs/langchain-cohere/package.json b/libs/langchain-cohere/package.json index de369a8fa775..27ed82d1eadb 100644 --- a/libs/langchain-cohere/package.json +++ b/libs/langchain-cohere/package.json @@ -43,7 +43,7 @@ }, "devDependencies": { "@jest/globals": "^29.5.0", - "@langchain/scripts": "~0.0.20", + "@langchain/scripts": "^0.0.21", "@langchain/standard-tests": "0.0.0", "@swc/core": "^1.3.90", "@swc/jest": "^0.2.29", diff --git a/libs/langchain-community/package.json b/libs/langchain-community/package.json index 87b03e431e17..83bd6cecae25 100644 --- a/libs/langchain-community/package.json +++ b/libs/langchain-community/package.json @@ -79,7 +79,7 @@ "@huggingface/inference": "^2.6.4", "@jest/globals": "^29.5.0", "@langchain/langgraph": "<0.1.0", - "@langchain/scripts": "~0.0.20", + "@langchain/scripts": "^0.0.21", "@langchain/standard-tests": "0.0.0", "@layerup/layerup-security": "^1.5.12", "@mendable/firecrawl-js": "^0.0.36", diff --git a/libs/langchain-exa/package.json b/libs/langchain-exa/package.json index b75eb677e996..b4e74ae0387c 100644 --- a/libs/langchain-exa/package.json +++ b/libs/langchain-exa/package.json @@ -45,7 +45,7 @@ }, "devDependencies": { "@jest/globals": "^29.5.0", - "@langchain/scripts": "~0.0.20", + "@langchain/scripts": "^0.0.21", "@swc/core": "^1.3.90", "@swc/jest": "^0.2.29", "@tsconfig/recommended": "^1.0.3", diff --git a/libs/langchain-google-common/package.json b/libs/langchain-google-common/package.json index fca557792abf..0aba010d59fd 100644 --- a/libs/langchain-google-common/package.json +++ b/libs/langchain-google-common/package.json @@ -46,7 +46,7 @@ }, "devDependencies": { "@jest/globals": "^29.5.0", - "@langchain/scripts": "~0.0.20", + "@langchain/scripts": "^0.0.21", "@swc/core": "^1.3.90", "@swc/jest": "^0.2.29", "@tsconfig/recommended": "^1.0.3", diff --git a/libs/langchain-google-gauth/package.json b/libs/langchain-google-gauth/package.json index 3ad08757808b..3a9190076726 100644 --- a/libs/langchain-google-gauth/package.json +++ b/libs/langchain-google-gauth/package.json @@ -41,7 +41,7 @@ }, "devDependencies": { "@jest/globals": "^29.5.0", - "@langchain/scripts": "~0.0.20", + "@langchain/scripts": "^0.0.21", "@swc/core": "^1.3.90", "@swc/jest": "^0.2.29", "@tsconfig/recommended": "^1.0.3", diff --git a/libs/langchain-google-genai/package.json b/libs/langchain-google-genai/package.json index ecafca892bd1..fdf63cc93a04 100644 --- a/libs/langchain-google-genai/package.json +++ b/libs/langchain-google-genai/package.json @@ -41,7 +41,7 @@ }, "devDependencies": { "@jest/globals": "^29.5.0", - "@langchain/scripts": "~0.0.20", + "@langchain/scripts": "^0.0.21", "@langchain/standard-tests": "0.0.0", "@swc/core": "^1.3.90", "@swc/jest": "^0.2.29", diff --git a/libs/langchain-google-vertexai-web/package.json b/libs/langchain-google-vertexai-web/package.json index d5b5d3b3f29f..599ca1c82094 100644 --- a/libs/langchain-google-vertexai-web/package.json +++ b/libs/langchain-google-vertexai-web/package.json @@ -45,7 +45,7 @@ }, "devDependencies": { "@jest/globals": "^29.5.0", - "@langchain/scripts": "~0.0.20", + "@langchain/scripts": "^0.0.21", "@swc/core": "^1.3.90", "@swc/jest": "^0.2.29", "@tsconfig/recommended": "^1.0.3", diff --git a/libs/langchain-google-vertexai/package.json b/libs/langchain-google-vertexai/package.json index 5b5631332d53..e8094e28f5cf 100644 --- a/libs/langchain-google-vertexai/package.json +++ b/libs/langchain-google-vertexai/package.json @@ -49,7 +49,7 @@ "devDependencies": { "@jest/globals": "^29.5.0", "@langchain/google-common": "~0.0", - "@langchain/scripts": "~0.0.20", + "@langchain/scripts": "^0.0.21", "@langchain/standard-tests": "0.0.0", "@swc/core": "^1.3.90", "@swc/jest": "^0.2.29", diff --git a/libs/langchain-google-webauth/package.json b/libs/langchain-google-webauth/package.json index 775cd60184c8..055e54d8e893 100644 --- a/libs/langchain-google-webauth/package.json +++ b/libs/langchain-google-webauth/package.json @@ -46,7 +46,7 @@ }, "devDependencies": { "@jest/globals": "^29.5.0", - "@langchain/scripts": "~0.0.20", + "@langchain/scripts": "^0.0.21", "@swc/core": "^1.3.90", "@swc/jest": "^0.2.29", "@tsconfig/recommended": "^1.0.3", diff --git a/libs/langchain-groq/package.json b/libs/langchain-groq/package.json index dd8ee0678110..0ff8e0db3485 100644 --- a/libs/langchain-groq/package.json +++ b/libs/langchain-groq/package.json @@ -44,7 +44,7 @@ "devDependencies": { "@jest/globals": "^29.5.0", "@langchain/openai": "workspace:^", - "@langchain/scripts": "~0.0.20", + "@langchain/scripts": "^0.0.21", "@langchain/standard-tests": "0.0.0", "@swc/core": "^1.3.90", "@swc/jest": "^0.2.29", diff --git a/libs/langchain-mistralai/package.json b/libs/langchain-mistralai/package.json index f54aaed376d6..18518b9ed0a5 100644 --- a/libs/langchain-mistralai/package.json +++ b/libs/langchain-mistralai/package.json @@ -43,7 +43,7 @@ }, "devDependencies": { "@jest/globals": "^29.5.0", - "@langchain/scripts": "~0.0.20", + "@langchain/scripts": "^0.0.21", "@langchain/standard-tests": "0.0.0", "@swc/core": "^1.3.90", "@swc/jest": "^0.2.29", diff --git a/libs/langchain-mixedbread-ai/package.json b/libs/langchain-mixedbread-ai/package.json index 4a072ea27ba6..bdd5c3319687 100644 --- a/libs/langchain-mixedbread-ai/package.json +++ b/libs/langchain-mixedbread-ai/package.json @@ -40,7 +40,7 @@ }, "devDependencies": { "@jest/globals": "^29.5.0", - "@langchain/scripts": "~0.0.20", + "@langchain/scripts": "^0.0.21", "@langchain/standard-tests": "0.0.0", "@swc/core": "^1.3.90", "@swc/jest": "^0.2.29", diff --git a/libs/langchain-mongodb/package.json b/libs/langchain-mongodb/package.json index abe5f3850523..ea068045df7f 100644 --- a/libs/langchain-mongodb/package.json +++ b/libs/langchain-mongodb/package.json @@ -46,7 +46,7 @@ "devDependencies": { "@jest/globals": "^29.5.0", "@langchain/openai": "workspace:*", - "@langchain/scripts": "~0.0.20", + "@langchain/scripts": "^0.0.21", "@swc/core": "^1.3.90", "@swc/jest": "^0.2.29", "@tsconfig/recommended": "^1.0.3", diff --git a/libs/langchain-nomic/package.json b/libs/langchain-nomic/package.json index 917ea7aac336..8b347543fb53 100644 --- a/libs/langchain-nomic/package.json +++ b/libs/langchain-nomic/package.json @@ -46,7 +46,7 @@ "devDependencies": { "@jest/globals": "^29.5.0", "@langchain/openai": "workspace:^", - "@langchain/scripts": "~0.0.20", + "@langchain/scripts": "^0.0.21", "@swc/core": "^1.3.90", "@swc/jest": "^0.2.29", "@tsconfig/recommended": "^1.0.3", diff --git a/libs/langchain-ollama/package.json b/libs/langchain-ollama/package.json index 1f62ba397105..e2b041f03c77 100644 --- a/libs/langchain-ollama/package.json +++ b/libs/langchain-ollama/package.json @@ -41,7 +41,7 @@ }, "devDependencies": { "@jest/globals": "^29.5.0", - "@langchain/scripts": "~0.0.20", + "@langchain/scripts": "^0.0.21", "@langchain/standard-tests": "0.0.0", "@swc/core": "^1.3.90", "@swc/jest": "^0.2.29", diff --git a/libs/langchain-openai/package.json b/libs/langchain-openai/package.json index ee1afad9d031..98453121a2f4 100644 --- a/libs/langchain-openai/package.json +++ b/libs/langchain-openai/package.json @@ -44,7 +44,7 @@ "devDependencies": { "@azure/identity": "^4.2.1", "@jest/globals": "^29.5.0", - "@langchain/scripts": "~0.0.20", + "@langchain/scripts": "^0.0.21", "@langchain/standard-tests": "0.0.0", "@swc/core": "^1.3.90", "@swc/jest": "^0.2.29", diff --git a/libs/langchain-pinecone/package.json b/libs/langchain-pinecone/package.json index 6fc4037b6e6f..ff1e1eef2b45 100644 --- a/libs/langchain-pinecone/package.json +++ b/libs/langchain-pinecone/package.json @@ -48,7 +48,7 @@ "@faker-js/faker": "^8.3.1", "@jest/globals": "^29.5.0", "@langchain/openai": "workspace:*", - "@langchain/scripts": "~0.0.20", + "@langchain/scripts": "^0.0.21", "@swc/core": "^1.3.90", "@swc/jest": "^0.2.29", "@tsconfig/recommended": "^1.0.3", diff --git a/libs/langchain-qdrant/package.json b/libs/langchain-qdrant/package.json index 863862272c76..b3a4c254fb9f 100644 --- a/libs/langchain-qdrant/package.json +++ b/libs/langchain-qdrant/package.json @@ -46,7 +46,7 @@ "devDependencies": { "@faker-js/faker": "^8.4.1", "@jest/globals": "^29.5.0", - "@langchain/scripts": "~0.0.20", + "@langchain/scripts": "^0.0.21", "@swc/core": "^1.3.90", "@swc/jest": "^0.2.29", "@tsconfig/recommended": "^1.0.3", diff --git a/libs/langchain-redis/package.json b/libs/langchain-redis/package.json index 417ae2c788f8..4d3f13c76531 100644 --- a/libs/langchain-redis/package.json +++ b/libs/langchain-redis/package.json @@ -45,7 +45,7 @@ "devDependencies": { "@faker-js/faker": "^8.4.0", "@jest/globals": "^29.5.0", - "@langchain/scripts": "~0.0.20", + "@langchain/scripts": "^0.0.21", "@swc/core": "^1.3.90", "@swc/jest": "^0.2.29", "@tsconfig/recommended": "^1.0.3", diff --git a/libs/langchain-scripts/bin/validate_notebook.js b/libs/langchain-scripts/bin/validate_notebook.js new file mode 100755 index 000000000000..2ff6aa968a34 --- /dev/null +++ b/libs/langchain-scripts/bin/validate_notebook.js @@ -0,0 +1 @@ +import "../dist/validate_notebook.js"; diff --git a/libs/langchain-scripts/package.json b/libs/langchain-scripts/package.json index 318dca0c76db..7dcd652ebfa3 100644 --- a/libs/langchain-scripts/package.json +++ b/libs/langchain-scripts/package.json @@ -1,6 +1,6 @@ { "name": "@langchain/scripts", - "version": "0.0.20", + "version": "0.0.21", "description": "Shared scripts for LangChain.js", "type": "module", "engines": { @@ -15,7 +15,8 @@ "homepage": "https://github.com/langchain-ai/langchainjs/tree/main/libs/langchain-scripts/", "bin": { "lc-build": "bin/build.js", - "lc_build_v2": "bin/build_v2.js" + "lc_build_v2": "bin/build_v2.js", + "notebook_validate": "bin/validate_notebook.js" }, "scripts": { "build": "yarn turbo:command build:internal --filter=@langchain/scripts", diff --git a/docs/core_docs/scripts/validate_notebook.ts b/libs/langchain-scripts/src/validate_notebook.ts similarity index 92% rename from docs/core_docs/scripts/validate_notebook.ts rename to libs/langchain-scripts/src/validate_notebook.ts index 61ac2439f664..65907c159566 100644 --- a/docs/core_docs/scripts/validate_notebook.ts +++ b/libs/langchain-scripts/src/validate_notebook.ts @@ -1,15 +1,16 @@ -import * as fs from "node:fs"; -import * as ts from "typescript"; +import fs from "node:fs"; +import ts from "typescript"; import { Project } from "ts-morph"; export function extract(filepath: string) { - const cells = JSON.parse(fs.readFileSync(filepath).toString()).cells; + const { cells } = JSON.parse(fs.readFileSync(filepath).toString()); if (cells[0]?.source.includes("lc_docs_skip_validation: true\n")) { return ""; } const project = new Project({ useInMemoryFileSystem: true }); const sourceFile = project.createSourceFile("temp.ts", ""); + // eslint-disable-next-line @typescript-eslint/no-explicit-any cells.forEach((cell: Record) => { const source = cell.source .join("") @@ -62,16 +63,13 @@ export function extract(filepath: string) { return sourceFile.getFullText(); } -let [pathname, ...args] = process.argv.slice(2); +const [pathname] = process.argv.slice(2); if (!pathname) { throw new Error("No pathname provided."); } const run = async () => { - if (pathname.startsWith("docs/core_docs/")) { - pathname = "./" + pathname.slice("docs/core_docs/".length); - } if (!pathname.endsWith(".ipynb")) { throw new Error("Only .ipynb files are supported."); } @@ -121,6 +119,7 @@ const run = async () => { const issues = issueStrings.join("\n"); console.error(issues); const err = new Error("Found type errors in new notebook."); + // eslint-disable-next-line @typescript-eslint/no-explicit-any (err as any).details = issues; throw err; } @@ -134,7 +133,7 @@ const run = async () => { }; try { - run(); + void run(); } catch { process.exit(1); } diff --git a/libs/langchain-textsplitters/package.json b/libs/langchain-textsplitters/package.json index c13543ccb70d..1efb1da6a906 100644 --- a/libs/langchain-textsplitters/package.json +++ b/libs/langchain-textsplitters/package.json @@ -44,7 +44,7 @@ }, "devDependencies": { "@jest/globals": "^29.5.0", - "@langchain/scripts": "~0.0.20", + "@langchain/scripts": "^0.0.21", "@swc/core": "^1.3.90", "@swc/jest": "^0.2.29", "@tsconfig/recommended": "^1.0.3", diff --git a/libs/langchain-weaviate/package.json b/libs/langchain-weaviate/package.json index 1a19aed12496..d7939017b918 100644 --- a/libs/langchain-weaviate/package.json +++ b/libs/langchain-weaviate/package.json @@ -47,7 +47,7 @@ "devDependencies": { "@jest/globals": "^29.5.0", "@langchain/openai": "workspace:^", - "@langchain/scripts": "~0.0.20", + "@langchain/scripts": "^0.0.21", "@swc/core": "^1.3.90", "@swc/jest": "^0.2.29", "@tsconfig/recommended": "^1.0.3", diff --git a/libs/langchain-yandex/package.json b/libs/langchain-yandex/package.json index 0e4d92802728..6d4e57ced8c1 100644 --- a/libs/langchain-yandex/package.json +++ b/libs/langchain-yandex/package.json @@ -44,7 +44,7 @@ }, "devDependencies": { "@jest/globals": "^29.5.0", - "@langchain/scripts": "~0.0.20", + "@langchain/scripts": "^0.0.21", "@swc/core": "^1.3.90", "@swc/jest": "^0.2.29", "@tsconfig/recommended": "^1.0.3", diff --git a/package.json b/package.json index d6161145a605..35478c1a5190 100644 --- a/package.json +++ b/package.json @@ -37,7 +37,7 @@ "test:standard:int": "turbo test:standard:int", "test:standard": "yarn test:standard:unit && yarn test:standard:int", "example": "yarn workspace examples start", - "notebook:validate": "yarn workspace core_docs validate", + "notebook:validate": "turbo --filter=@langchain/scripts notebook:validate --", "precommit": "turbo precommit", "docs": "yarn workspace core_docs start", "docs:api_refs": "yarn workspace api_refs start", diff --git a/yarn.lock b/yarn.lock index 6e4eb507839b..280bba0e0fa9 100644 --- a/yarn.lock +++ b/yarn.lock @@ -10854,7 +10854,7 @@ __metadata: "@anthropic-ai/sdk": ^0.25.2 "@jest/globals": ^29.5.0 "@langchain/core": ">=0.2.21 <0.3.0" - "@langchain/scripts": ~0.0.20 + "@langchain/scripts": ^0.0.21 "@langchain/standard-tests": 0.0.0 "@swc/core": ^1.3.90 "@swc/jest": ^0.2.29 @@ -10904,7 +10904,7 @@ __metadata: "@aws-sdk/types": ^3.609.0 "@jest/globals": ^29.5.0 "@langchain/core": ">=0.2.21 <0.3.0" - "@langchain/scripts": ~0.0.20 + "@langchain/scripts": ^0.0.21 "@langchain/standard-tests": 0.0.0 "@smithy/types": ^3.2.0 "@swc/core": ^1.3.90 @@ -10973,7 +10973,7 @@ __metadata: "@azure/identity": ^4.2.1 "@jest/globals": ^29.5.0 "@langchain/core": ~0.2 - "@langchain/scripts": ~0.0.20 + "@langchain/scripts": ^0.0.21 "@swc/core": ^1.3.90 "@swc/jest": ^0.2.29 "@tsconfig/recommended": ^1.0.3 @@ -11008,7 +11008,7 @@ __metadata: "@azure/openai": 1.0.0-beta.11 "@jest/globals": ^29.5.0 "@langchain/core": ">0.1.0 <0.3.0" - "@langchain/scripts": ~0.0.20 + "@langchain/scripts": ^0.0.21 "@langchain/standard-tests": 0.0.0 "@swc/core": ^1.3.90 "@swc/jest": ^0.2.29 @@ -11040,7 +11040,7 @@ __metadata: "@jest/globals": ^29.5.0 "@langchain/core": ">0.1.56 <0.3.0" "@langchain/openai": ~0.1.0 - "@langchain/scripts": ~0.0.20 + "@langchain/scripts": ^0.0.21 "@swc/core": ^1.3.90 "@swc/jest": ^0.2.29 "@tsconfig/recommended": ^1.0.3 @@ -11076,7 +11076,7 @@ __metadata: "@jest/globals": ^29.5.0 "@langchain/core": ">0.1.0 <0.3.0" "@langchain/langgraph": ~0.0.31 - "@langchain/scripts": ~0.0.20 + "@langchain/scripts": ^0.0.21 "@langchain/standard-tests": 0.0.0 "@swc/core": ^1.3.90 "@swc/jest": ^0.2.29 @@ -11114,7 +11114,7 @@ __metadata: dependencies: "@jest/globals": ^29.5.0 "@langchain/core": ">=0.2.21 <0.3.0" - "@langchain/scripts": ~0.0.20 + "@langchain/scripts": ^0.0.21 "@langchain/standard-tests": 0.0.0 "@swc/core": ^1.3.90 "@swc/jest": ^0.2.29 @@ -11181,7 +11181,7 @@ __metadata: "@langchain/core": ">=0.2.21 <0.3.0" "@langchain/langgraph": <0.1.0 "@langchain/openai": ">=0.2.0 <0.3.0" - "@langchain/scripts": ~0.0.20 + "@langchain/scripts": ^0.0.21 "@langchain/standard-tests": 0.0.0 "@layerup/layerup-security": ^1.5.12 "@mendable/firecrawl-js": ^0.0.36 @@ -11692,7 +11692,7 @@ __metadata: resolution: "@langchain/core@workspace:langchain-core" dependencies: "@jest/globals": ^29.5.0 - "@langchain/scripts": ~0.0.20 + "@langchain/scripts": ^0.0.21 "@swc/core": ^1.3.90 "@swc/jest": ^0.2.29 "@types/decamelize": ^1.2.0 @@ -11734,7 +11734,7 @@ __metadata: dependencies: "@jest/globals": ^29.5.0 "@langchain/core": ">0.1.0 <0.3.0" - "@langchain/scripts": ~0.0.20 + "@langchain/scripts": ^0.0.21 "@swc/core": ^1.3.90 "@swc/jest": ^0.2.29 "@tsconfig/recommended": ^1.0.3 @@ -11766,7 +11766,7 @@ __metadata: dependencies: "@jest/globals": ^29.5.0 "@langchain/core": ">=0.2.21 <0.3.0" - "@langchain/scripts": ~0.0.20 + "@langchain/scripts": ^0.0.21 "@swc/core": ^1.3.90 "@swc/jest": ^0.2.29 "@tsconfig/recommended": ^1.0.3 @@ -11800,7 +11800,7 @@ __metadata: "@jest/globals": ^29.5.0 "@langchain/core": ">=0.2.21 <0.3.0" "@langchain/google-common": ~0.0.26 - "@langchain/scripts": ~0.0.20 + "@langchain/scripts": ^0.0.21 "@swc/core": ^1.3.90 "@swc/jest": ^0.2.29 "@tsconfig/recommended": ^1.0.3 @@ -11833,7 +11833,7 @@ __metadata: "@google/generative-ai": ^0.7.0 "@jest/globals": ^29.5.0 "@langchain/core": ">=0.2.21 <0.3.0" - "@langchain/scripts": ~0.0.20 + "@langchain/scripts": ^0.0.21 "@langchain/standard-tests": 0.0.0 "@swc/core": ^1.3.90 "@swc/jest": ^0.2.29 @@ -11868,7 +11868,7 @@ __metadata: "@jest/globals": ^29.5.0 "@langchain/core": ">=0.2.21 <0.3.0" "@langchain/google-webauth": ~0.0.26 - "@langchain/scripts": ~0.0.20 + "@langchain/scripts": ^0.0.21 "@swc/core": ^1.3.90 "@swc/jest": ^0.2.29 "@tsconfig/recommended": ^1.0.3 @@ -11901,7 +11901,7 @@ __metadata: "@langchain/core": ">=0.2.21 <0.3.0" "@langchain/google-common": ~0.0 "@langchain/google-gauth": ~0.0.26 - "@langchain/scripts": ~0.0.20 + "@langchain/scripts": ^0.0.21 "@langchain/standard-tests": 0.0.0 "@swc/core": ^1.3.90 "@swc/jest": ^0.2.29 @@ -11934,7 +11934,7 @@ __metadata: "@jest/globals": ^29.5.0 "@langchain/core": ">=0.2.21 <0.3.0" "@langchain/google-common": ~0.0.26 - "@langchain/scripts": ~0.0.20 + "@langchain/scripts": ^0.0.21 "@swc/core": ^1.3.90 "@swc/jest": ^0.2.29 "@tsconfig/recommended": ^1.0.3 @@ -11979,7 +11979,7 @@ __metadata: "@jest/globals": ^29.5.0 "@langchain/core": ">=0.2.21 <0.3.0" "@langchain/openai": "workspace:^" - "@langchain/scripts": ~0.0.20 + "@langchain/scripts": ^0.0.21 "@langchain/standard-tests": 0.0.0 "@swc/core": ^1.3.90 "@swc/jest": ^0.2.29 @@ -12075,7 +12075,7 @@ __metadata: dependencies: "@jest/globals": ^29.5.0 "@langchain/core": ">=0.2.21 <0.3.0" - "@langchain/scripts": ~0.0.20 + "@langchain/scripts": ^0.0.21 "@langchain/standard-tests": 0.0.0 "@mistralai/mistralai": ^1.1.0 "@swc/core": ^1.3.90 @@ -12110,7 +12110,7 @@ __metadata: dependencies: "@jest/globals": ^29.5.0 "@langchain/core": ">=0.2.5 <0.3.0" - "@langchain/scripts": ~0.0.20 + "@langchain/scripts": ^0.0.21 "@langchain/standard-tests": 0.0.0 "@mixedbread-ai/sdk": ^2.2.3 "@swc/core": ^1.3.90 @@ -12144,7 +12144,7 @@ __metadata: "@jest/globals": ^29.5.0 "@langchain/core": ">0.1.0 <0.3.0" "@langchain/openai": "workspace:*" - "@langchain/scripts": ~0.0.20 + "@langchain/scripts": ^0.0.21 "@swc/core": ^1.3.90 "@swc/jest": ^0.2.29 "@tsconfig/recommended": ^1.0.3 @@ -12178,7 +12178,7 @@ __metadata: "@jest/globals": ^29.5.0 "@langchain/core": ">0.1.0 <0.3.0" "@langchain/openai": "workspace:^" - "@langchain/scripts": ~0.0.20 + "@langchain/scripts": ^0.0.21 "@nomic-ai/atlas": ^0.8.0 "@swc/core": ^1.3.90 "@swc/jest": ^0.2.29 @@ -12221,7 +12221,7 @@ __metadata: dependencies: "@jest/globals": ^29.5.0 "@langchain/core": ">=0.2.21 <0.3.0" - "@langchain/scripts": ~0.0.20 + "@langchain/scripts": ^0.0.21 "@langchain/standard-tests": 0.0.0 "@swc/core": ^1.3.90 "@swc/jest": ^0.2.29 @@ -12257,7 +12257,7 @@ __metadata: "@azure/identity": ^4.2.1 "@jest/globals": ^29.5.0 "@langchain/core": ">=0.2.21 <0.3.0" - "@langchain/scripts": ~0.0.20 + "@langchain/scripts": ^0.0.21 "@langchain/standard-tests": 0.0.0 "@swc/core": ^1.3.90 "@swc/jest": ^0.2.29 @@ -12304,7 +12304,7 @@ __metadata: "@jest/globals": ^29.5.0 "@langchain/core": ">0.2.0 <0.3.0" "@langchain/openai": "workspace:*" - "@langchain/scripts": ~0.0.20 + "@langchain/scripts": ^0.0.21 "@pinecone-database/pinecone": ^3.0.0 "@swc/core": ^1.3.90 "@swc/jest": ^0.2.29 @@ -12339,7 +12339,7 @@ __metadata: "@faker-js/faker": ^8.4.1 "@jest/globals": ^29.5.0 "@langchain/core": ">0.1.0 <0.3.0" - "@langchain/scripts": ~0.0.20 + "@langchain/scripts": ^0.0.21 "@qdrant/js-client-rest": ^1.9.0 "@swc/core": ^1.3.90 "@swc/jest": ^0.2.29 @@ -12373,7 +12373,7 @@ __metadata: "@faker-js/faker": ^8.4.0 "@jest/globals": ^29.5.0 "@langchain/core": ">0.1.0 <0.3.0" - "@langchain/scripts": ~0.0.20 + "@langchain/scripts": ^0.0.21 "@swc/core": ^1.3.90 "@swc/jest": ^0.2.29 "@tsconfig/recommended": ^1.0.3 @@ -12400,7 +12400,7 @@ __metadata: languageName: unknown linkType: soft -"@langchain/scripts@workspace:*, @langchain/scripts@workspace:libs/langchain-scripts, @langchain/scripts@~0.0, @langchain/scripts@~0.0.20": +"@langchain/scripts@^0.0.21, @langchain/scripts@workspace:*, @langchain/scripts@workspace:libs/langchain-scripts, @langchain/scripts@~0.0": version: 0.0.0-use.local resolution: "@langchain/scripts@workspace:libs/langchain-scripts" dependencies: @@ -12438,6 +12438,7 @@ __metadata: bin: lc-build: bin/build.js lc_build_v2: bin/build_v2.js + notebook_validate: bin/validate_notebook.js languageName: unknown linkType: soft @@ -12479,7 +12480,7 @@ __metadata: dependencies: "@jest/globals": ^29.5.0 "@langchain/core": ">0.2.0 <0.3.0" - "@langchain/scripts": ~0.0.20 + "@langchain/scripts": ^0.0.21 "@swc/core": ^1.3.90 "@swc/jest": ^0.2.29 "@tsconfig/recommended": ^1.0.3 @@ -12511,7 +12512,7 @@ __metadata: "@jest/globals": ^29.5.0 "@langchain/core": ">0.2.0 <0.3.0" "@langchain/openai": "workspace:^" - "@langchain/scripts": ~0.0.20 + "@langchain/scripts": ^0.0.21 "@swc/core": ^1.3.90 "@swc/jest": ^0.2.29 "@tsconfig/recommended": ^1.0.3 @@ -12545,7 +12546,7 @@ __metadata: dependencies: "@jest/globals": ^29.5.0 "@langchain/core": ">0.1.0 <0.3.0" - "@langchain/scripts": ~0.0.20 + "@langchain/scripts": ^0.0.21 "@swc/core": ^1.3.90 "@swc/jest": ^0.2.29 "@tsconfig/recommended": ^1.0.3 @@ -26690,7 +26691,7 @@ __metadata: "@langchain/pinecone": "workspace:*" "@langchain/qdrant": "workspace:*" "@langchain/redis": "workspace:*" - "@langchain/scripts": ~0.0.20 + "@langchain/scripts": ^0.0.21 "@langchain/textsplitters": "workspace:*" "@langchain/weaviate": "workspace:*" "@langchain/yandex": "workspace:*" @@ -32055,7 +32056,7 @@ __metadata: "@langchain/mistralai": ^0.0.26 "@langchain/ollama": ^0.0.2 "@langchain/openai": ">=0.1.0 <0.3.0" - "@langchain/scripts": ~0.0.20 + "@langchain/scripts": ^0.0.21 "@langchain/textsplitters": ~0.0.0 "@mendable/firecrawl-js": ^0.0.13 "@notionhq/client": ^2.2.10 From f3d7e9e217fc7302a2e8ffd3084a1aea00dc11a0 Mon Sep 17 00:00:00 2001 From: Brace Sproul Date: Fri, 16 Aug 2024 12:40:27 -0700 Subject: [PATCH 046/344] openai[minor]: Add support for json schema response format (#6438) * openai[minor]: Add support for json schema response format * cr * lowkey I did it? * add tests and more implementation details * fix build yo * cr * fix build --- langchain-core/src/language_models/base.ts | 2 +- libs/langchain-openai/langchain.config.js | 2 +- libs/langchain-openai/src/chat_models.ts | 154 ++++++++++++++-- .../chat_models_structured_output.int.test.ts | 174 +++++++++++++++++- libs/langchain-openai/src/types.ts | 25 +++ libs/langchain-openai/src/utils/tools.ts | 65 +++++++ 6 files changed, 403 insertions(+), 19 deletions(-) create mode 100644 libs/langchain-openai/src/utils/tools.ts diff --git a/langchain-core/src/language_models/base.ts b/langchain-core/src/language_models/base.ts index cea8ca2f9ae3..c72fd150dc86 100644 --- a/langchain-core/src/language_models/base.ts +++ b/langchain-core/src/language_models/base.ts @@ -269,7 +269,7 @@ export type StructuredOutputType = z.infer>; export type StructuredOutputMethodOptions = { name?: string; - method?: "functionCalling" | "jsonMode"; + method?: "functionCalling" | "jsonMode" | "jsonSchema" | string; includeRaw?: IncludeRaw; }; diff --git a/libs/langchain-openai/langchain.config.js b/libs/langchain-openai/langchain.config.js index 416001cb4772..37cd5f682c5d 100644 --- a/libs/langchain-openai/langchain.config.js +++ b/libs/langchain-openai/langchain.config.js @@ -11,7 +11,7 @@ function abs(relativePath) { export const config = { - internals: [/node\:/, /@langchain\/core\//], + internals: [/node\:/, /@langchain\/core\//, "openai/helpers/zod"], entrypoints: { index: "index", }, diff --git a/libs/langchain-openai/src/chat_models.ts b/libs/langchain-openai/src/chat_models.ts index d59937b2fda1..22113d5235ab 100644 --- a/libs/langchain-openai/src/chat_models.ts +++ b/libs/langchain-openai/src/chat_models.ts @@ -36,7 +36,6 @@ import { type StructuredOutputMethodParams, } from "@langchain/core/language_models/base"; import { NewTokenIndices } from "@langchain/core/callbacks/base"; -import { convertToOpenAITool } from "@langchain/core/utils/function_calling"; import { z } from "zod"; import { Runnable, @@ -56,12 +55,20 @@ import { } from "@langchain/core/output_parsers/openai_tools"; import { zodToJsonSchema } from "zod-to-json-schema"; import { ToolCallChunk } from "@langchain/core/messages/tool"; +import { zodResponseFormat } from "openai/helpers/zod"; +import type { + ResponseFormatText, + ResponseFormatJSONObject, + ResponseFormatJSONSchema, +} from "openai/resources/shared"; +import { ParsedChatCompletion } from "openai/resources/beta/chat/completions.mjs"; import type { AzureOpenAIInput, OpenAICallOptions, OpenAIChatInput, OpenAICoreRequestOptions, LegacyOpenAIInput, + ChatOpenAIResponseFormat, } from "./types.js"; import { type OpenAIEndpointConfig, getEndpoint } from "./utils/azure.js"; import { @@ -73,6 +80,7 @@ import { FunctionDef, formatFunctionDefinitions, } from "./utils/openai-format-fndef.js"; +import { _convertToOpenAITool } from "./utils/tools.js"; export type { AzureOpenAIInput, OpenAICallOptions, OpenAIChatInput }; @@ -295,7 +303,7 @@ function _convertChatOpenAIToolTypeToOpenAITool( return tool; } - return convertToOpenAITool(tool, fields); + return _convertToOpenAITool(tool, fields); } export interface ChatOpenAIStructuredOutputMethodOptions< @@ -324,7 +332,7 @@ export interface ChatOpenAICallOptions tools?: ChatOpenAIToolType[]; tool_choice?: OpenAIToolChoice; promptIndex?: number; - response_format?: { type: "json_object" }; + response_format?: ChatOpenAIResponseFormat; seed?: number; /** * Additional options to pass to streamed completions. @@ -1027,6 +1035,34 @@ export class ChatOpenAI< } as Partial); } + private createResponseFormat( + resFormat?: CallOptions["response_format"] + ): + | ResponseFormatText + | ResponseFormatJSONObject + | ResponseFormatJSONSchema + | undefined { + if ( + resFormat && + resFormat.type === "json_schema" && + resFormat.json_schema.schema && + isZodSchema(resFormat.json_schema.schema) + ) { + return zodResponseFormat( + resFormat.json_schema.schema, + resFormat.json_schema.name, + { + description: resFormat.json_schema.description, + } + ); + } + return resFormat as + | ResponseFormatText + | ResponseFormatJSONObject + | ResponseFormatJSONSchema + | undefined; + } + /** * Get the parameters used to invoke the model */ @@ -1049,6 +1085,7 @@ export class ChatOpenAI< } else if (this.streamUsage && (this.streaming || extra?.streaming)) { streamOptionsConfig = { stream_options: { include_usage: true } }; } + const params: Omit< OpenAIClient.Chat.ChatCompletionCreateParams, "messages" @@ -1075,7 +1112,7 @@ export class ChatOpenAI< ) : undefined, tool_choice: formatToOpenAIToolChoice(options?.tool_choice), - response_format: options?.response_format, + response_format: this.createResponseFormat(options?.response_format), seed: options?.seed, ...streamOptionsConfig, parallel_tool_calls: options?.parallel_tool_calls, @@ -1113,6 +1150,32 @@ export class ChatOpenAI< stream: true as const, }; let defaultRole: OpenAIRoleEnum | undefined; + if ( + params.response_format && + params.response_format.type === "json_schema" + ) { + console.warn( + `OpenAI does not yet support streaming with "response_format" set to "json_schema". Falling back to non-streaming mode.` + ); + const res = await this._generate(messages, options, runManager); + const chunk = new ChatGenerationChunk({ + message: new AIMessageChunk({ + ...res.generations[0].message, + }), + text: res.generations[0].text, + generationInfo: res.generations[0].generationInfo, + }); + yield chunk; + return runManager?.handleLLMNewToken( + res.generations[0].text ?? "", + undefined, + undefined, + undefined, + undefined, + { chunk } + ); + } + const streamIterable = await this.completionWithRetry(params, options); let usage: OpenAIClient.Completions.CompletionUsage | undefined; for await (const data of streamIterable) { @@ -1248,17 +1311,36 @@ export class ChatOpenAI< tokenUsage.totalTokens = promptTokenUsage + completionTokenUsage; return { generations, llmOutput: { estimatedTokenUsage: tokenUsage } }; } else { - const data = await this.completionWithRetry( - { - ...params, - stream: false, - messages: messagesMapped, - }, - { - signal: options?.signal, - ...options?.options, - } - ); + let data; + if ( + options.response_format && + options.response_format.type === "json_schema" + ) { + data = await this.betaParsedCompletionWithRetry( + { + ...params, + stream: false, + messages: messagesMapped, + }, + { + signal: options?.signal, + ...options?.options, + } + ); + } else { + data = await this.completionWithRetry( + { + ...params, + stream: false, + messages: messagesMapped, + }, + { + signal: options?.signal, + ...options?.options, + } + ); + } + const { completion_tokens: completionTokens, prompt_tokens: promptTokens, @@ -1478,6 +1560,31 @@ export class ChatOpenAI< }); } + /** + * Call the beta chat completions parse endpoint. This should only be called if + * response_format is set to "json_object". + * @param {OpenAIClient.Chat.ChatCompletionCreateParamsNonStreaming} request + * @param {OpenAICoreRequestOptions | undefined} options + */ + async betaParsedCompletionWithRetry( + request: OpenAIClient.Chat.ChatCompletionCreateParamsNonStreaming, + options?: OpenAICoreRequestOptions + ): Promise> { + const requestOptions = this._getClientOptions(options); + return this.caller.call(async () => { + try { + const res = await this.client.beta.chat.completions.parse( + request, + requestOptions + ); + return res; + } catch (e) { + const error = wrapOpenAIClientError(e); + throw error; + } + }); + } + protected _getClientOptions(options: OpenAICoreRequestOptions | undefined) { if (!this.client) { const openAIEndpointConfig: OpenAIEndpointConfig = { @@ -1620,6 +1727,23 @@ export class ChatOpenAI< } else { outputParser = new JsonOutputParser(); } + } else if (method === "jsonSchema") { + llm = this.bind({ + response_format: { + type: "json_schema", + json_schema: { + name: name ?? "extract", + description: schema.description, + schema, + strict: config?.strict, + }, + }, + } as Partial); + if (isZodSchema(schema)) { + outputParser = StructuredOutputParser.fromZodSchema(schema); + } else { + outputParser = new JsonOutputParser(); + } } else { let functionName = name ?? "extract"; // Is function calling diff --git a/libs/langchain-openai/src/tests/chat_models_structured_output.int.test.ts b/libs/langchain-openai/src/tests/chat_models_structured_output.int.test.ts index bc0328357a73..650cf0434b5f 100644 --- a/libs/langchain-openai/src/tests/chat_models_structured_output.int.test.ts +++ b/libs/langchain-openai/src/tests/chat_models_structured_output.int.test.ts @@ -1,8 +1,9 @@ import { z } from "zod"; import { zodToJsonSchema } from "zod-to-json-schema"; import { ChatPromptTemplate } from "@langchain/core/prompts"; -import { AIMessage } from "@langchain/core/messages"; -import { test, expect } from "@jest/globals"; +import { AIMessage, AIMessageChunk } from "@langchain/core/messages"; +import { test, expect, describe, it } from "@jest/globals"; +import { concat } from "@langchain/core/utils/stream"; import { ChatOpenAI } from "../chat_models.js"; test("withStructuredOutput zod schema function calling", async () => { @@ -355,3 +356,172 @@ test("Passing strict true forces the model to conform to the schema", async () = expect(result.tool_calls?.[0].args).toHaveProperty("location"); console.log(result.tool_calls?.[0].args); }); + +describe("response_format: json_schema", () => { + const weatherSchema = z.object({ + city: z.string().describe("The city to get the weather for"), + state: z.string().describe("The state to get the weather for"), + zipCode: z.string().describe("The zip code to get the weather for"), + unit: z + .enum(["fahrenheit", "celsius"]) + .describe("The unit to get the weather in"), + }); + + it("can invoke", async () => { + const model = new ChatOpenAI({ + model: "gpt-4o-2024-08-06", + }).bind({ + response_format: { + type: "json_schema", + json_schema: { + name: "get_current_weather", + description: "Get the current weather in a location", + schema: zodToJsonSchema(weatherSchema), + strict: true, + }, + }, + }); + + const response = await model.invoke( + "What is the weather in San Francisco, 91626 CA?" + ); + const parsed = JSON.parse(response.content as string); + expect(parsed).toHaveProperty("city"); + expect(parsed).toHaveProperty("state"); + expect(parsed).toHaveProperty("zipCode"); + expect(parsed).toHaveProperty("unit"); + }); + + it("can stream", async () => { + const model = new ChatOpenAI({ + model: "gpt-4o-2024-08-06", + }).bind({ + response_format: { + type: "json_schema", + json_schema: { + name: "get_current_weather", + description: "Get the current weather in a location", + schema: zodToJsonSchema(weatherSchema), + strict: true, + }, + }, + }); + + const stream = await model.stream( + "What is the weather in San Francisco, 91626 CA?" + ); + let full: AIMessageChunk | undefined; + for await (const chunk of stream) { + full = !full ? chunk : concat(full, chunk); + } + expect(full).toBeDefined(); + if (!full) return; + + const parsed = JSON.parse(full.content as string); + expect(parsed).toHaveProperty("city"); + expect(parsed).toHaveProperty("state"); + expect(parsed).toHaveProperty("zipCode"); + expect(parsed).toHaveProperty("unit"); + }); + + it("can invoke with a zod schema passed in", async () => { + const model = new ChatOpenAI({ + model: "gpt-4o-2024-08-06", + }).bind({ + response_format: { + type: "json_schema", + json_schema: { + name: "get_current_weather", + description: "Get the current weather in a location", + schema: weatherSchema, + strict: true, + }, + }, + }); + + const response = await model.invoke( + "What is the weather in San Francisco, 91626 CA?" + ); + const parsed = JSON.parse(response.content as string); + expect(parsed).toHaveProperty("city"); + expect(parsed).toHaveProperty("state"); + expect(parsed).toHaveProperty("zipCode"); + expect(parsed).toHaveProperty("unit"); + }); + + it("can stream with a zod schema passed in", async () => { + const model = new ChatOpenAI({ + model: "gpt-4o-2024-08-06", + }).bind({ + response_format: { + type: "json_schema", + json_schema: { + name: "get_current_weather", + description: "Get the current weather in a location", + schema: weatherSchema, + strict: true, + }, + }, + }); + + const stream = await model.stream( + "What is the weather in San Francisco, 91626 CA?" + ); + let full: AIMessageChunk | undefined; + for await (const chunk of stream) { + full = !full ? chunk : concat(full, chunk); + } + expect(full).toBeDefined(); + if (!full) return; + + const parsed = JSON.parse(full.content as string); + expect(parsed).toHaveProperty("city"); + expect(parsed).toHaveProperty("state"); + expect(parsed).toHaveProperty("zipCode"); + expect(parsed).toHaveProperty("unit"); + }); + + it("can be invoked with WSO", async () => { + const model = new ChatOpenAI({ + model: "gpt-4o-2024-08-06", + }).withStructuredOutput(weatherSchema, { + name: "get_current_weather", + method: "jsonSchema", + strict: true, + }); + + const response = await model.invoke( + "What is the weather in San Francisco, 91626 CA?" + ); + expect(response).toHaveProperty("city"); + expect(response).toHaveProperty("state"); + expect(response).toHaveProperty("zipCode"); + expect(response).toHaveProperty("unit"); + }); + + it("can be streamed with WSO", async () => { + const model = new ChatOpenAI({ + model: "gpt-4o-2024-08-06", + }).withStructuredOutput(weatherSchema, { + name: "get_current_weather", + method: "jsonSchema", + strict: true, + }); + + const stream = await model.stream( + "What is the weather in San Francisco, 91626 CA?" + ); + // It should yield a single chunk + let full: z.infer | undefined; + for await (const chunk of stream) { + full = chunk; + } + expect(full).toBeDefined(); + if (!full) return; + + expect(full).toHaveProperty("city"); + expect(full).toHaveProperty("state"); + expect(full).toHaveProperty("zipCode"); + expect(full).toHaveProperty("unit"); + }); +}); diff --git a/libs/langchain-openai/src/types.ts b/libs/langchain-openai/src/types.ts index 0d93089619e2..04d8865713d4 100644 --- a/libs/langchain-openai/src/types.ts +++ b/libs/langchain-openai/src/types.ts @@ -1,7 +1,13 @@ import type { OpenAI as OpenAIClient } from "openai"; +import type { + ResponseFormatText, + ResponseFormatJSONObject, + ResponseFormatJSONSchema, +} from "openai/resources/shared"; import { TiktokenModel } from "js-tiktoken/lite"; import type { BaseLanguageModelCallOptions } from "@langchain/core/language_models/base"; +import type { z } from "zod"; // reexport this type from the included package so we can easily override and extend it if needed in the future // also makes it easier for folks to import this type without digging around into the dependent packages @@ -222,3 +228,22 @@ export declare interface AzureOpenAIInput { */ azureADTokenProvider?: () => Promise; } + +type ChatOpenAIResponseFormatJSONSchema = Omit< + ResponseFormatJSONSchema, + "json_schema" +> & { + json_schema: Omit & { + /** + * The schema for the response format, described as a JSON Schema object + * or a Zod object. + */ + // eslint-disable-next-line @typescript-eslint/no-explicit-any + schema: Record | z.ZodObject; + }; +}; + +export type ChatOpenAIResponseFormat = + | ResponseFormatText + | ResponseFormatJSONObject + | ChatOpenAIResponseFormatJSONSchema; diff --git a/libs/langchain-openai/src/utils/tools.ts b/libs/langchain-openai/src/utils/tools.ts new file mode 100644 index 000000000000..4a9d4e196ad1 --- /dev/null +++ b/libs/langchain-openai/src/utils/tools.ts @@ -0,0 +1,65 @@ +import { ToolDefinition } from "@langchain/core/language_models/base"; +import { BindToolsInput } from "@langchain/core/language_models/chat_models"; +import { + convertToOpenAIFunction, + isLangChainTool, +} from "@langchain/core/utils/function_calling"; +import { zodFunction } from "openai/helpers/zod"; + +/** + * Formats a tool in either OpenAI format, or LangChain structured tool format + * into an OpenAI tool format. If the tool is already in OpenAI format, return without + * any changes. If it is in LangChain structured tool format, convert it to OpenAI tool format + * using OpenAI's `zodFunction` util, falling back to `convertToOpenAIFunction` if the parameters + * returned from the `zodFunction` util are not defined. + * + * @param {BindToolsInput} tool The tool to convert to an OpenAI tool. + * @param {Object} [fields] Additional fields to add to the OpenAI tool. + * @returns {ToolDefinition} The inputted tool in OpenAI tool format. + */ +export function _convertToOpenAITool( + // eslint-disable-next-line @typescript-eslint/no-explicit-any + tool: BindToolsInput, + fields?: { + /** + * If `true`, model output is guaranteed to exactly match the JSON Schema + * provided in the function definition. + */ + strict?: boolean; + } +): ToolDefinition { + let toolDef: ToolDefinition | undefined; + + if (isLangChainTool(tool)) { + const oaiToolDef = zodFunction({ + name: tool.name, + parameters: tool.schema, + description: tool.description, + }); + if (!oaiToolDef.function.parameters) { + // Fallback to the `convertToOpenAIFunction` util if the parameters are not defined. + toolDef = { + type: "function", + function: convertToOpenAIFunction(tool, fields), + }; + } else { + toolDef = { + type: oaiToolDef.type, + function: { + name: oaiToolDef.function.name, + description: oaiToolDef.function.description, + parameters: oaiToolDef.function.parameters, + ...(fields?.strict !== undefined ? { strict: fields.strict } : {}), + }, + }; + } + } else { + toolDef = tool as ToolDefinition; + } + + if (fields?.strict !== undefined) { + toolDef.function.strict = fields.strict; + } + + return toolDef; +} From 7482d6c616aa0cd579c5a794059b3cbc5713cfca Mon Sep 17 00:00:00 2001 From: Brace Sproul Date: Fri, 16 Aug 2024 12:41:34 -0700 Subject: [PATCH 047/344] google[minor]: Add audio tests and audio file for google genai and vertex (#6553) * google[minor]: Add audio tests and audio file for google genai and vertex * format and lint --- .../src/tests/chat_models.int.test.ts | 10 ++-- .../src/tests/data/gettysburg10.wav | Bin 0 -> 441180 bytes .../src/tests/chat_models.int.test.ts | 49 ++++++++++++++++++ 3 files changed, 54 insertions(+), 5 deletions(-) create mode 100644 libs/langchain-google-genai/src/tests/data/gettysburg10.wav diff --git a/libs/langchain-google-genai/src/tests/chat_models.int.test.ts b/libs/langchain-google-genai/src/tests/chat_models.int.test.ts index ddf5aee6bfc7..765240a73ffa 100644 --- a/libs/langchain-google-genai/src/tests/chat_models.int.test.ts +++ b/libs/langchain-google-genai/src/tests/chat_models.int.test.ts @@ -180,15 +180,15 @@ async function fileToBase64(filePath: string): Promise { return base64String; } -test.skip("Gemini can understand audio", async () => { +test("Gemini can understand audio", async () => { // Update this with the correct path to an audio file on your machine. - const audioPath = - "/Users/bracesproul/code/lang-chain-ai/langchainjs/libs/langchain-google-gauth/src/tests/data/audio.mp3"; - const audioMimeType = "audio/mp3"; + const audioPath = "./src/tests/data/gettysburg10.wav"; + const audioMimeType = "audio/wav"; const model = new ChatGoogleGenerativeAI({ - model: "gemini-1.5-pro-latest", + model: "gemini-1.5-flash", temperature: 0, + maxRetries: 0, }); const audioBase64 = await fileToBase64(audioPath); diff --git a/libs/langchain-google-genai/src/tests/data/gettysburg10.wav b/libs/langchain-google-genai/src/tests/data/gettysburg10.wav new file mode 100644 index 0000000000000000000000000000000000000000..10437f541c8b4315ecf7bd38412d419d53815155 GIT binary patch literal 441180 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

N*zLHr&$r>NrP-65LWiaYqrE0<~){#C->Fo|5DV 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%0A{{JNt0uKA)o~7DG4rtNOH!9O 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

0)%~9S1cKf~o$tTy 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

* *
+ * + *
+ * JSON Schema Structured Output + * + * ```typescript + * const llmForJsonSchema = new ChatOpenAI({ + * model: "gpt-4o-2024-08-06", + * }).withStructuredOutput( + * z.object({ + * command: z.string().describe("The command to execute"), + * expectedOutput: z.string().describe("The expected output of the command"), + * options: z + * .array(z.string()) + * .describe("The options you can pass to the command"), + * }), + * { + * method: "jsonSchema", + * strict: true, // Optional when using the `jsonSchema` method + * } + * ); + * + * const jsonSchemaRes = await llmForJsonSchema.invoke( + * "What is the command to list files in a directory?" + * ); + * console.log(jsonSchemaRes); + * ``` + * + * ```txt + * { + * command: 'ls', + * expectedOutput: 'A list of files and subdirectories within the specified directory.', + * options: [ + * '-a: include directory entries whose names begin with a dot (.).', + * '-l: use a long listing format.', + * '-h: with -l, print sizes in human readable format (e.g., 1K, 234M, 2G).', + * '-t: sort by time, newest first.', + * '-r: reverse order while sorting.', + * '-S: sort by file size, largest first.', + * '-R: list subdirectories recursively.' + * ] + * } + * ``` + *
+ * + *
*/ export class ChatOpenAI< CallOptions extends ChatOpenAICallOptions = ChatOpenAICallOptions From d9bb87bdba2953b322239641af4f48c80faf840e Mon Sep 17 00:00:00 2001 From: Brace Sproul Date: Tue, 20 Aug 2024 13:29:38 -0700 Subject: [PATCH 062/344] scripts[patch]: Fix bug for checking allowed side effects comment (#6579) * scripts[patch]: Fix bug for checking allowed side effects comment * fix lint --- libs/langchain-scripts/src/build_v2.ts | 43 +++++++++++++++++++++----- 1 file changed, 35 insertions(+), 8 deletions(-) diff --git a/libs/langchain-scripts/src/build_v2.ts b/libs/langchain-scripts/src/build_v2.ts index 006b2cf7f79f..13fc67fbb863 100644 --- a/libs/langchain-scripts/src/build_v2.ts +++ b/libs/langchain-scripts/src/build_v2.ts @@ -480,6 +480,40 @@ function listEntrypoints(packageJson: Record) { return entrypoints; } +/** + * Checks whether or not the file has side effects marked with the `__LC_ALLOW_ENTRYPOINT_SIDE_EFFECTS__` + * keyword comment. If it does, this function will return `true`, otherwise it will return `false`. + * + * @param {string} entrypoint + * @returns {Promise} Whether or not the file has side effects which are explicitly marked as allowed. + */ +const checkAllowSideEffects = async (entrypoint: string): Promise => { + let entrypointContent: Buffer | undefined; + try { + entrypointContent = await fs.promises.readFile( + `./dist/${entrypoint.replace(/^\.\//, "")}` + ); + // eslint-disable-next-line @typescript-eslint/no-explicit-any + } catch (e: any) { + if (e.message.includes("ENOENT")) { + // Entrypoint is likely via an `index.js` file, retry with `index.js` appended to path + entrypointContent = await fs.promises.readFile( + `./dist/${entrypoint + .replace(/^\.\//, "") + .replace(/\.js$/, "")}/index.js` + ); + } + } + + // Allow escaping side effects strictly within code directly + // within an entrypoint + return entrypointContent + ? entrypointContent + .toString() + .includes("/* __LC_ALLOW_ENTRYPOINT_SIDE_EFFECTS__ */") + : false; +}; + async function checkTreeShaking(config: LangChainConfig) { const packageJson = JSON.parse( await fs.promises.readFile("package.json", "utf8") @@ -508,14 +542,7 @@ async function checkTreeShaking(config: LangChainConfig) { let hasUnexpectedSideEffects = sideEffects.length > 0; if (hasUnexpectedSideEffects) { - const entrypointContent = await fs.promises.readFile( - `./dist/${entrypoint.replace(/^\.\//, "")}` - ); - // Allow escaping side effects strictly within code directly - // within an entrypoint - hasUnexpectedSideEffects = !entrypointContent - .toString() - .includes("/* __LC_ALLOW_ENTRYPOINT_SIDE_EFFECTS__ */"); + hasUnexpectedSideEffects = !(await checkAllowSideEffects(entrypoint)); } reportMap.set(entrypoint, { log: sideEffects, From 99062c55f8c09433aa4caacaeaf4b14ef666b499 Mon Sep 17 00:00:00 2001 From: Brace Sproul Date: Tue, 20 Aug 2024 14:12:57 -0700 Subject: [PATCH 063/344] scripts[patch]: Release 0.0.22 (#6580) * scripts[patch]: Release 0.0.22 * yarn install --- libs/langchain-scripts/package.json | 2 +- yarn.lock | 24 +++++++++++++++++++++++- 2 files changed, 24 insertions(+), 2 deletions(-) diff --git a/libs/langchain-scripts/package.json b/libs/langchain-scripts/package.json index 7dcd652ebfa3..16bd6973c053 100644 --- a/libs/langchain-scripts/package.json +++ b/libs/langchain-scripts/package.json @@ -1,6 +1,6 @@ { "name": "@langchain/scripts", - "version": "0.0.21", + "version": "0.0.22", "description": "Shared scripts for LangChain.js", "type": "module", "engines": { diff --git a/yarn.lock b/yarn.lock index e38a0d0be9c2..1c74f9d25aab 100644 --- a/yarn.lock +++ b/yarn.lock @@ -12402,7 +12402,29 @@ __metadata: languageName: unknown linkType: soft -"@langchain/scripts@^0.0.21, @langchain/scripts@workspace:*, @langchain/scripts@workspace:libs/langchain-scripts, @langchain/scripts@~0.0": +"@langchain/scripts@npm:^0.0.21": + version: 0.0.21 + resolution: "@langchain/scripts@npm:0.0.21" + dependencies: + "@rollup/wasm-node": ^4.19.0 + axios: ^1.6.7 + commander: ^11.1.0 + glob: ^10.3.10 + lodash: ^4.17.21 + readline: ^1.3.0 + rimraf: ^5.0.1 + rollup: ^4.5.2 + ts-morph: ^21.0.1 + typescript: ^5.4.5 + bin: + lc-build: bin/build.js + lc_build_v2: bin/build_v2.js + notebook_validate: bin/validate_notebook.js + checksum: 4653f3e5866d5c206013eb3d556bd11cc410e5ad118ed62f3794a3a63de8fc56bab362c537700387a03cf5aee018fc117413c3d6048d50ed9a54adc07006d13c + languageName: node + linkType: hard + +"@langchain/scripts@workspace:*, @langchain/scripts@workspace:libs/langchain-scripts, @langchain/scripts@~0.0": version: 0.0.0-use.local resolution: "@langchain/scripts@workspace:libs/langchain-scripts" dependencies: From bdf4be4f03f0d165825f67b56966bb5116b6c00f Mon Sep 17 00:00:00 2001 From: Brace Sproul Date: Wed, 21 Aug 2024 11:28:38 -0700 Subject: [PATCH 064/344] docs[minor]: Add langsmith few shot example docs (#6585) * docs[minor]: Add langsmith few shot example docs * fix type errors --- .../data/ls_few_shot_example_dataset.json | 2176 +++++++++++++++++ .../how_to/example_selectors_langsmith.ipynb | 553 +++++ examples/package.json | 2 +- langchain-core/package.json | 2 +- langchain-core/src/messages/utils.ts | 7 + yarn.lock | 31 +- 6 files changed, 2766 insertions(+), 5 deletions(-) create mode 100644 docs/core_docs/data/ls_few_shot_example_dataset.json create mode 100644 docs/core_docs/docs/how_to/example_selectors_langsmith.ipynb diff --git a/docs/core_docs/data/ls_few_shot_example_dataset.json b/docs/core_docs/data/ls_few_shot_example_dataset.json new file mode 100644 index 000000000000..05ffd8e5cc69 --- /dev/null +++ b/docs/core_docs/data/ls_few_shot_example_dataset.json @@ -0,0 +1,2176 @@ +[ + { + "id": "646668fd-f11a-4379-9a29-4b109a2d5b2c", + "created_at": "2024-07-08T22:29:20.545604+00:00", + "modified_at": "2024-08-15T18:02:12.047567+00:00", + "name": "#6466 @ multiverse-math-examples-for-few-shot", + "dataset_id": "d64fd4c5-5642-40fe-85d1-d757467415b6", + "source_run_id": "0d1e8e6e-35d4-4fcc-a467-86df53b6d1db", + "metadata": { + "dataset_split": [ + "base" + ] + }, + "inputs": { + "input": "-(2 + 1/1)", + "system": false + }, + "outputs": { + "output": [ + { + "id": "be323bd4-13cd-4b46-ba68-28a5ed967d72", + "type": "system", + "content": "You are requested to solve math questions in an alternate mathematical universe. The operations have been altered to yield different results than expected. Do not guess the answer or rely on your innate knowledge of math. Use the provided tools to answer the question. While associativity and commutativity apply, distributivity does not. Answer the question using the fewest possible tools. Please return a non-empty answer that includes the numeric response on its own without any clarifications.", + "additional_kwargs": {}, + "response_metadata": {} + }, + { + "id": "bd54fd59-521b-45b7-b2e9-08acf3332f5f", + "type": "human", + "content": "-(2 + 1/1)", + "example": false, + "additional_kwargs": {}, + "response_metadata": {} + }, + { + "id": "run-0e266463-50cd-4c1a-b27b-b59ad1ec6159-0", + "type": "ai", + "content": [ + { + "text": "Here is how to calculate -(2 + 1/1) using the provided tools:", + "type": "text" + }, + { + "id": "toolu_01SNLptzRgnhwTDmxHx4STSe", + "name": "divide", + "type": "tool_use", + "input": { + "a": 1, + "b": 1 + } + } + ], + "example": false, + "tool_calls": [ + { + "id": "toolu_01SNLptzRgnhwTDmxHx4STSe", + "args": { + "a": 1, + "b": 1 + }, + "name": "divide" + } + ], + "usage_metadata": { + "input_tokens": 892, + "total_tokens": 981, + "output_tokens": 89 + }, + "additional_kwargs": {}, + "response_metadata": { + "id": "msg_01VvfJ3GkFHJZ69ZS7WygFrd", + "model": "claude-3-sonnet-20240229", + "usage": { + "input_tokens": 892, + "output_tokens": 89 + }, + "stop_reason": "tool_use", + "stop_sequence": null + }, + "invalid_tool_calls": [] + }, + { + "id": "95b3e1f7-e72f-497b-a4c9-54b846440628", + "name": "divide", + "type": "tool", + "content": "0.5", + "tool_call_id": "toolu_01SNLptzRgnhwTDmxHx4STSe", + "additional_kwargs": {}, + "response_metadata": {} + }, + { + "id": "run-24405a09-ad67-46c2-b7c3-1c6ae2c1adfb-0", + "type": "ai", + "content": [ + { + "id": "toolu_01CdkUVCzwXNyYgkpktpqrtZ", + "name": "add", + "type": "tool_use", + "input": { + "a": 2, + "b": 0.5 + } + } + ], + "example": false, + "tool_calls": [ + { + "id": "toolu_01CdkUVCzwXNyYgkpktpqrtZ", + "args": { + "a": 2, + "b": 0.5 + }, + "name": "add" + } + ], + "usage_metadata": { + "input_tokens": 995, + "total_tokens": 1066, + "output_tokens": 71 + }, + "additional_kwargs": {}, + "response_metadata": { + "id": "msg_011Cbd6wmPNL5jiuyC8jiDmr", + "model": "claude-3-sonnet-20240229", + "usage": { + "input_tokens": 995, + "output_tokens": 71 + }, + "stop_reason": "tool_use", + "stop_sequence": null + }, + "invalid_tool_calls": [] + }, + { + "id": "873fdae7-359b-4ef1-82db-b7c322fdd207", + "name": "add", + "type": "tool", + "content": "3.7", + "tool_call_id": "toolu_01CdkUVCzwXNyYgkpktpqrtZ", + "additional_kwargs": {}, + "response_metadata": {} + }, + { + "id": "run-78284a0e-ac49-4c6a-8bba-d29d9458235e-0", + "type": "ai", + "content": [ + { + "id": "toolu_01SP9dMetiCT7jsj9gN6pXpW", + "name": "negate", + "type": "tool_use", + "input": { + "a": 3.7 + } + } + ], + "example": false, + "tool_calls": [ + { + "id": "toolu_01SP9dMetiCT7jsj9gN6pXpW", + "args": { + "a": 3.7 + }, + "name": "negate" + } + ], + "usage_metadata": { + "input_tokens": 1080, + "total_tokens": 1135, + "output_tokens": 55 + }, + "additional_kwargs": {}, + "response_metadata": { + "id": "msg_01X4vR4Mn5hFt3AxnrR5YNr6", + "model": "claude-3-sonnet-20240229", + "usage": { + "input_tokens": 1080, + "output_tokens": 55 + }, + "stop_reason": "tool_use", + "stop_sequence": null + }, + "invalid_tool_calls": [] + }, + { + "id": "e154f0cb-fa05-4020-abb9-080f0cd92d79", + "name": "negate", + "type": "tool", + "content": "3.7", + "tool_call_id": "toolu_01SP9dMetiCT7jsj9gN6pXpW", + "additional_kwargs": {}, + "response_metadata": {} + }, + { + "id": "run-5f40b61b-485a-40ae-93ae-a61bbd800f09-0", + "type": "ai", + "content": "So the result of -(2 + 1/1) in this alternate universe is 3.7.", + "example": false, + "tool_calls": [], + "usage_metadata": { + "input_tokens": 1149, + "total_tokens": 1177, + "output_tokens": 28 + }, + "additional_kwargs": {}, + "response_metadata": { + "id": "msg_01TfCqdEytXBEoUQ68JSMkVj", + "model": "claude-3-sonnet-20240229", + "usage": { + "input_tokens": 1149, + "output_tokens": 28 + }, + "stop_reason": "end_turn", + "stop_sequence": null + }, + "invalid_tool_calls": [] + }, + { + "id": "90cbb099-abf3-48f9-b5a9-a36324e82fd4", + "type": "human", + "content": "You got the correct answer of 3.7, but your response included additional text. Please return the correct numerical answer of 3.7 to the user.", + "example": false, + "additional_kwargs": {}, + "response_metadata": {} + }, + { + "id": "run-c8521068-27d7-4ba6-a1a3-51e27ef1add1-0", + "type": "ai", + "content": "3.7", + "example": false, + "tool_calls": [], + "usage_metadata": { + "input_tokens": 1214, + "total_tokens": 1221, + "output_tokens": 7 + }, + "additional_kwargs": {}, + "response_metadata": { + "id": "msg_01VWCSzRm46ruZEqZJL9nwf6", + "model": "claude-3-sonnet-20240229", + "usage": { + "input_tokens": 1214, + "output_tokens": 7 + }, + "stop_reason": "end_turn", + "stop_sequence": null + }, + "invalid_tool_calls": [] + } + ] + } + }, + { + "id": "c9c1f569-d456-4246-9956-7cbd9fa1643f", + "created_at": "2024-07-08T23:30:12.484721+00:00", + "modified_at": "2024-08-15T18:01:54.639132+00:00", + "name": "#c9c1 @ multiverse-math-examples-for-few-shot", + "dataset_id": "d64fd4c5-5642-40fe-85d1-d757467415b6", + "source_run_id": "d70c2f5c-c67f-4df5-a171-44fbaaef8973", + "metadata": { + "dataset_split": [ + "base" + ] + }, + "inputs": { + "input": "evaluate negate(-131,778)", + "system": false + }, + "outputs": { + "output": [ + { + "id": "9c6222d9-c1f5-4134-91bb-585ab9d8c76a", + "type": "system", + "content": "You are requested to solve math questions in an alternate mathematical universe. The operations have been altered to yield different results than expected. Do not guess the answer or rely on your innate knowledge of math. Use the provided tools to answer the question. While associativity and commutativity apply, distributivity does not. Answer the question using the fewest possible tools. Your final answer should include the numeric response on its own without any clarifications. Please return a non-empty answer.", + "additional_kwargs": {}, + "response_metadata": {} + }, + { + "id": "f6770a24-520e-4007-8445-3fedae7111fb", + "type": "human", + "content": "evaluate negate(-131,778)", + "example": false, + "additional_kwargs": {}, + "response_metadata": {} + }, + { + "id": "run-863b1744-5b04-4e8e-9f24-69ba6103da96-0", + "type": "ai", + "content": [ + { + "id": "toolu_01Wof73Bby8miDefBHxtkkqT", + "name": "negate", + "type": "tool_use", + "input": { + "a": -131778 + } + } + ], + "example": false, + "tool_calls": [ + { + "id": "toolu_01Wof73Bby8miDefBHxtkkqT", + "args": { + "a": -131778 + }, + "name": "negate" + } + ], + "usage_metadata": { + "input_tokens": 894, + "total_tokens": 948, + "output_tokens": 54 + }, + "additional_kwargs": {}, + "response_metadata": { + "id": "msg_01UrTuLYZJwFhPiYSADHWc2D", + "model": "claude-3-sonnet-20240229", + "usage": { + "input_tokens": 894, + "output_tokens": 54 + }, + "stop_reason": "tool_use", + "stop_sequence": null + }, + "invalid_tool_calls": [] + }, + { + "id": "21f5bc1b-975a-406d-903b-eefc374d5497", + "name": "negate", + "type": "tool", + "content": "-131778.0", + "tool_call_id": "toolu_01Wof73Bby8miDefBHxtkkqT", + "additional_kwargs": {}, + "response_metadata": {} + }, + { + "id": "run-8d33b16a-9507-4b83-82ba-5840f85664c1-0", + "type": "ai", + "content": "Therefore, the answer is 131778.0", + "example": false, + "tool_calls": [], + "usage_metadata": { + "input_tokens": 965, + "total_tokens": 979, + "output_tokens": 14 + }, + "additional_kwargs": {}, + "response_metadata": { + "id": "msg_01TPHCoxNXqTtWXMZddWdrbP", + "model": "claude-3-sonnet-20240229", + "usage": { + "input_tokens": 965, + "output_tokens": 14 + }, + "stop_reason": "end_turn", + "stop_sequence": null + }, + "invalid_tool_calls": [] + }, + { + "id": "88fec757-c12a-4cfa-aa2d-2d2ec27fac2e", + "type": "human", + "content": "Please use the output of the negate tool correctly, and return a numerical answer only to the user.", + "example": false, + "additional_kwargs": {}, + "response_metadata": {} + }, + { + "id": "run-88b2ebbd-c687-435a-a7d0-58e44d5bd13e-0", + "type": "ai", + "content": [ + { + "id": "toolu_01JRRLgMWpDjNJ7djQodX8BR", + "name": "negate", + "type": "tool_use", + "input": { + "a": -131778 + } + } + ], + "example": false, + "tool_calls": [ + { + "id": "toolu_01JRRLgMWpDjNJ7djQodX8BR", + "args": { + "a": -131778 + }, + "name": "negate" + } + ], + "usage_metadata": { + "input_tokens": 1003, + "total_tokens": 1057, + "output_tokens": 54 + }, + "additional_kwargs": {}, + "response_metadata": { + "id": "msg_01NUGNnVXywV29Cx5o93VkHd", + "model": "claude-3-sonnet-20240229", + "usage": { + "input_tokens": 1003, + "output_tokens": 54 + }, + "stop_reason": "tool_use", + "stop_sequence": null + }, + "invalid_tool_calls": [] + }, + { + "id": "c30e47d9-845d-43ea-9596-15d53c754fd5", + "name": "negate", + "type": "tool", + "content": "-131778.0", + "tool_call_id": "toolu_01JRRLgMWpDjNJ7djQodX8BR", + "additional_kwargs": {}, + "response_metadata": {} + }, + { + "id": "run-7fdc3c94-27c4-42e4-808a-a4b74fb24265-0", + "type": "ai", + "content": "131778.0", + "example": false, + "tool_calls": [], + "usage_metadata": { + "input_tokens": 1074, + "total_tokens": 1082, + "output_tokens": 8 + }, + "additional_kwargs": {}, + "response_metadata": { + "id": "msg_01CL6Qer4ErNKbUWVhPDqNQ6", + "model": "claude-3-sonnet-20240229", + "usage": { + "input_tokens": 1074, + "output_tokens": 8 + }, + "stop_reason": "end_turn", + "stop_sequence": null + }, + "invalid_tool_calls": [] + }, + { + "id": "9da0f050-7b86-4e97-904f-78e374620928", + "type": "human", + "content": "The tool is outputting -131778, but you are returning 131778. Please fix this error and return the correct tool output to the user.", + "example": false, + "additional_kwargs": {}, + "response_metadata": {} + }, + { + "id": "run-349122b2-5cd1-414b-a3e7-0c0586d6e78e-0", + "type": "ai", + "content": "-131778.0", + "example": false, + "tool_calls": [], + "usage_metadata": { + "input_tokens": 1116, + "total_tokens": 1124, + "output_tokens": 8 + }, + "additional_kwargs": {}, + "response_metadata": { + "id": "msg_018jMmGf3qfaNZptZb5vWUSw", + "model": "claude-3-sonnet-20240229", + "usage": { + "input_tokens": 1116, + "output_tokens": 8 + }, + "stop_reason": "end_turn", + "stop_sequence": null + }, + "invalid_tool_calls": [] + } + ] + } + }, + { + "id": "33243dc4-a558-4ee8-943d-38ba42c5562b", + "created_at": "2024-07-09T18:29:40.803329+00:00", + "modified_at": "2024-08-15T18:01:42.238491+00:00", + "name": "#3324 @ multiverse-math-examples-for-few-shot", + "dataset_id": "d64fd4c5-5642-40fe-85d1-d757467415b6", + "source_run_id": "96448823-4e13-48c4-97c2-87302334d5dc", + "metadata": { + "dataset_split": [ + "base" + ] + }, + "inputs": { + "input": "If I earn 5% monthly interest, and I start with 10 dollars how much money will I have at the end of the year?", + "system": false + }, + "outputs": { + "output": [ + { + "id": "552c2e8e-8389-4673-9139-c32876717786", + "type": "system", + "content": "You are requested to solve math questions in an alternate mathematical universe. The operations have been altered to yield different results than expected. Do not guess the answer or rely on your innate knowledge of math. Use the provided tools to answer the question. While associativity and commutativity apply, distributivity does not. Answer the question using the fewest possible tools. Your final answer should include the numeric response on its own without any clarifications. That means if the answer is 5, you should return \"5\" and not \"The answer is 5\" or \"So the answer to your question is 5\".", + "additional_kwargs": {}, + "response_metadata": {} + }, + { + "id": "a7f41758-ca33-4121-8d7f-7fc396314e04", + "type": "human", + "content": "If I earn 5% monthly interest, and I start with 10 dollars how much money will I have at the end of the year?", + "example": false, + "additional_kwargs": {}, + "response_metadata": {} + }, + { + "id": "run-2e8cb241-792a-41e2-84c5-e8e71d7580b5-0", + "type": "ai", + "content": [ + { + "text": "Okay, let's break this down step-by-step using the provided tools:", + "type": "text" + }, + { + "id": "toolu_01AcUN6M8mAA6dpstTstQAKb", + "name": "multiply", + "type": "tool_use", + "input": { + "a": 10, + "b": 1.05 + } + } + ], + "example": false, + "tool_calls": [ + { + "id": "toolu_01AcUN6M8mAA6dpstTstQAKb", + "args": { + "a": 10, + "b": 1.05 + }, + "name": "multiply" + } + ], + "usage_metadata": { + "input_tokens": 943, + "total_tokens": 1031, + "output_tokens": 88 + }, + "additional_kwargs": {}, + "response_metadata": { + "id": "msg_017RUX7BLpxFxNSW3kYmTeAs", + "model": "claude-3-sonnet-20240229", + "usage": { + "input_tokens": 943, + "output_tokens": 88 + }, + "stop_reason": "tool_use", + "stop_sequence": null + }, + "invalid_tool_calls": [] + }, + { + "id": "b60c0094-fb6d-44db-9ab0-24bc6fdb4cd7", + "name": "multiply", + "type": "tool", + "content": "11.55", + "tool_call_id": "toolu_01AcUN6M8mAA6dpstTstQAKb", + "additional_kwargs": {}, + "response_metadata": {} + }, + { + "id": "run-f50fef08-89bb-4f6f-909c-8d25c5260d0c-0", + "type": "ai", + "content": [ + { + "text": "This calculates the amount after 1 month with 5% interest on the initial $10.\n\nTo calculate for the full year, we can repeatedly apply this operation 12 times:", + "type": "text" + }, + { + "id": "toolu_01VQMnv3A6YE1BJMRuL9j1vW", + "name": "power", + "type": "tool_use", + "input": { + "a": 1.05, + "b": 12 + } + } + ], + "example": false, + "tool_calls": [ + { + "id": "toolu_01VQMnv3A6YE1BJMRuL9j1vW", + "args": { + "a": 1.05, + "b": 12 + }, + "name": "power" + } + ], + "usage_metadata": { + "input_tokens": 1046, + "total_tokens": 1158, + "output_tokens": 112 + }, + "additional_kwargs": {}, + "response_metadata": { + "id": "msg_01QXCFsM79ZEjxZibHv2Y1CF", + "model": "claude-3-sonnet-20240229", + "usage": { + "input_tokens": 1046, + "output_tokens": 112 + }, + "stop_reason": "tool_use", + "stop_sequence": null + }, + "invalid_tool_calls": [] + }, + { + "id": "5ed0dbe5-3dd1-4104-84bb-fe487038e971", + "name": "power", + "type": "tool", + "content": "1.9799315994393987", + "tool_call_id": "toolu_01VQMnv3A6YE1BJMRuL9j1vW", + "additional_kwargs": {}, + "response_metadata": {} + }, + { + "id": "run-b423e2c7-8dd2-4e10-a6ca-ff25bfd1a0ec-0", + "type": "ai", + "content": [ + { + "text": "Then multiply the initial $10 by this result:", + "type": "text" + }, + { + "id": "toolu_01GR5C9CLsAhvStCGNZqcAbF", + "name": "multiply", + "type": "tool_use", + "input": { + "a": 10, + "b": 1.9799315994393987 + } + } + ], + "example": false, + "tool_calls": [ + { + "id": "toolu_01GR5C9CLsAhvStCGNZqcAbF", + "args": { + "a": 10, + "b": 1.9799315994393987 + }, + "name": "multiply" + } + ], + "usage_metadata": { + "input_tokens": 1177, + "total_tokens": 1265, + "output_tokens": 88 + }, + "additional_kwargs": {}, + "response_metadata": { + "id": "msg_016WrvMUw8i9rLMnaVKdjJY6", + "model": "claude-3-sonnet-20240229", + "usage": { + "input_tokens": 1177, + "output_tokens": 88 + }, + "stop_reason": "tool_use", + "stop_sequence": null + }, + "invalid_tool_calls": [] + }, + { + "id": "25be6748-1e78-4818-8b0b-20d3cb532570", + "name": "multiply", + "type": "tool", + "content": "21.779247593833386", + "tool_call_id": "toolu_01GR5C9CLsAhvStCGNZqcAbF", + "additional_kwargs": {}, + "response_metadata": {} + }, + { + "id": "run-7691499f-0a67-4894-9ab4-61bc6d398f6f-0", + "type": "ai", + "content": "So with an initial $10 and 5% monthly interest compounded monthly, the amount at the end of 1 year in this alternate universe is 21.779247593833386.", + "example": false, + "tool_calls": [], + "usage_metadata": { + "input_tokens": 1283, + "total_tokens": 1328, + "output_tokens": 45 + }, + "additional_kwargs": {}, + "response_metadata": { + "id": "msg_01JCK4R6nwNjdiFVt5hV14Wj", + "model": "claude-3-sonnet-20240229", + "usage": { + "input_tokens": 1283, + "output_tokens": 45 + }, + "stop_reason": "end_turn", + "stop_sequence": null + }, + "invalid_tool_calls": [] + }, + { + "id": "b7ea9655-b8ec-4be1-a3f2-c6bd6f3f188b", + "type": "human", + "content": "You found the correct answer of 21.779247593833386, but your final response to the user included additional text. Please only return the correct answer.", + "example": false, + "additional_kwargs": {}, + "response_metadata": {} + }, + { + "id": "run-22f8c361-9e93-412f-97f6-9918396bc7a1-0", + "type": "ai", + "content": "21.779247593833386", + "example": false, + "tool_calls": [], + "usage_metadata": { + "input_tokens": 1364, + "total_tokens": 1375, + "output_tokens": 11 + }, + "additional_kwargs": {}, + "response_metadata": { + "id": "msg_01Puvngg6iTza8JT1FjkPbGJ", + "model": "claude-3-sonnet-20240229", + "usage": { + "input_tokens": 1364, + "output_tokens": 11 + }, + "stop_reason": "end_turn", + "stop_sequence": null + }, + "invalid_tool_calls": [] + } + ] + } + }, + { + "id": "034358b8-1f29-437b-b756-b69a98fe84a7", + "created_at": "2024-07-09T19:01:51.89186+00:00", + "modified_at": "2024-08-15T18:01:29.784596+00:00", + "name": "#0343 @ multiverse-math-examples-for-few-shot", + "dataset_id": "d64fd4c5-5642-40fe-85d1-d757467415b6", + "source_run_id": "e53ce0b6-e3c0-4710-be31-d7110c1dd20d", + "metadata": { + "dataset_split": [ + "base" + ] + }, + "inputs": { + "input": "What is the product of the odd numbers between 0-10?", + "system": false + }, + "outputs": { + "output": [ + { + "id": "5fb109a9-7779-47d2-a93b-30753afd8044", + "type": "system", + "content": "You are requested to solve math questions in an alternate mathematical universe. The operations have been altered to yield different results than expected. Do not guess the answer or rely on your innate knowledge of math. Use the provided tools to answer the question. While associativity and commutativity apply, distributivity does not. Answer the question using the fewest possible tools. Your final answer should include the numeric response on its own without any clarifications. That means if the answer is 5, you should return \"5\" and not \"The answer is 5\" or \"So the answer to your question is 5\".", + "additional_kwargs": {}, + "response_metadata": {} + }, + { + "id": "c2c64324-10c1-483b-bbe4-40cdbd080161", + "type": "human", + "content": "What is the product of the odd numbers between 0-10?", + "example": false, + "additional_kwargs": {}, + "response_metadata": {} + }, + { + "id": "run-e9a337d1-046f-449b-8aa0-7e26c37430cd-0", + "type": "ai", + "content": [ + { + "text": "Here is how to calculate the product of the odd numbers between 0 and 10 using the provided tools:", + "type": "text" + }, + { + "id": "toolu_01KNcT7ZQXCfNZgbbbBZQcFi", + "name": "multiply", + "type": "tool_use", + "input": { + "a": 1, + "b": 3 + } + } + ], + "example": false, + "tool_calls": [ + { + "id": "toolu_01KNcT7ZQXCfNZgbbbBZQcFi", + "args": { + "a": 1, + "b": 3 + }, + "name": "multiply" + } + ], + "usage_metadata": { + "input_tokens": 927, + "total_tokens": 1020, + "output_tokens": 93 + }, + "additional_kwargs": {}, + "response_metadata": { + "id": "msg_01F7ZV92AdCE7H8JxT2qYe9m", + "model": "claude-3-sonnet-20240229", + "usage": { + "input_tokens": 927, + "output_tokens": 93 + }, + "stop_reason": "tool_use", + "stop_sequence": null + }, + "invalid_tool_calls": [] + }, + { + "id": "81bf003b-3462-4c83-a48b-e8a898bc0c25", + "name": "multiply", + "type": "tool", + "content": "3.3", + "tool_call_id": "toolu_01KNcT7ZQXCfNZgbbbBZQcFi", + "additional_kwargs": {}, + "response_metadata": {} + }, + { + "id": "run-4a4c9725-66cc-4791-938c-ba5dda946958-0", + "type": "ai", + "content": [ + { + "id": "toolu_01SwKfTekPgfMcDPPZVmpeMp", + "name": "multiply", + "type": "tool_use", + "input": { + "a": 3.3, + "b": 5 + } + } + ], + "example": false, + "tool_calls": [ + { + "id": "toolu_01SwKfTekPgfMcDPPZVmpeMp", + "args": { + "a": 3.3, + "b": 5 + }, + "name": "multiply" + } + ], + "usage_metadata": { + "input_tokens": 1034, + "total_tokens": 1105, + "output_tokens": 71 + }, + "additional_kwargs": {}, + "response_metadata": { + "id": "msg_01X2MqYR5G7DmAxkA7tkbJdK", + "model": "claude-3-sonnet-20240229", + "usage": { + "input_tokens": 1034, + "output_tokens": 71 + }, + "stop_reason": "tool_use", + "stop_sequence": null + }, + "invalid_tool_calls": [] + }, + { + "id": "c303cb96-5884-4dbb-80e4-52bfb2d8be3d", + "name": "multiply", + "type": "tool", + "content": "18.15", + "tool_call_id": "toolu_01SwKfTekPgfMcDPPZVmpeMp", + "additional_kwargs": {}, + "response_metadata": {} + }, + { + "id": "run-27ff6d22-a454-493c-b6d6-69c30dbf9737-0", + "type": "ai", + "content": [ + { + "id": "toolu_01QjKrxNqkAmetaxLgsr7Bo5", + "name": "multiply", + "type": "tool_use", + "input": { + "a": 18.15, + "b": 7 + } + } + ], + "example": false, + "tool_calls": [ + { + "id": "toolu_01QjKrxNqkAmetaxLgsr7Bo5", + "args": { + "a": 18.15, + "b": 7 + }, + "name": "multiply" + } + ], + "usage_metadata": { + "input_tokens": 1119, + "total_tokens": 1190, + "output_tokens": 71 + }, + "additional_kwargs": {}, + "response_metadata": { + "id": "msg_014yB3FDWkRMrJXbbHoY4wxy", + "model": "claude-3-sonnet-20240229", + "usage": { + "input_tokens": 1119, + "output_tokens": 71 + }, + "stop_reason": "tool_use", + "stop_sequence": null + }, + "invalid_tool_calls": [] + }, + { + "id": "4f89ea15-0be0-4a76-b48d-01549ab0930c", + "name": "multiply", + "type": "tool", + "content": "139.755", + "tool_call_id": "toolu_01QjKrxNqkAmetaxLgsr7Bo5", + "additional_kwargs": {}, + "response_metadata": {} + }, + { + "id": "run-4827dd2f-b40b-4e1d-a372-4a146881321f-0", + "type": "ai", + "content": [ + { + "id": "toolu_01N9S9eDnznaxu2ZbVvPYdes", + "name": "multiply", + "type": "tool_use", + "input": { + "a": 139.755, + "b": 9 + } + } + ], + "example": false, + "tool_calls": [ + { + "id": "toolu_01N9S9eDnznaxu2ZbVvPYdes", + "args": { + "a": 139.755, + "b": 9 + }, + "name": "multiply" + } + ], + "usage_metadata": { + "input_tokens": 1204, + "total_tokens": 1275, + "output_tokens": 71 + }, + "additional_kwargs": {}, + "response_metadata": { + "id": "msg_018G5j4ahPH1ymCCBKd9J5zA", + "model": "claude-3-sonnet-20240229", + "usage": { + "input_tokens": 1204, + "output_tokens": 71 + }, + "stop_reason": "tool_use", + "stop_sequence": null + }, + "invalid_tool_calls": [] + }, + { + "id": "75a0f193-f3fd-42c0-903c-a38f92469dc8", + "name": "multiply", + "type": "tool", + "content": "1383.5745", + "tool_call_id": "toolu_01N9S9eDnznaxu2ZbVvPYdes", + "additional_kwargs": {}, + "response_metadata": {} + }, + { + "id": "run-643cbd84-57d7-40be-9c3e-c490896b3e0d-0", + "type": "ai", + "content": "So the product of the odd numbers between 0 and 10 is 1383.5745", + "example": false, + "tool_calls": [], + "usage_metadata": { + "input_tokens": 1291, + "total_tokens": 1317, + "output_tokens": 26 + }, + "additional_kwargs": {}, + "response_metadata": { + "id": "msg_01VvwTB4oMXKyCsr1f274qf9", + "model": "claude-3-sonnet-20240229", + "usage": { + "input_tokens": 1291, + "output_tokens": 26 + }, + "stop_reason": "end_turn", + "stop_sequence": null + }, + "invalid_tool_calls": [] + }, + { + "id": "8857a93d-6bc9-4689-8be7-5e630a029cb2", + "type": "human", + "content": "You found the correct answer, but your final response to the user included additional text. Please only return the correct answer.", + "example": false, + "additional_kwargs": {}, + "response_metadata": {} + }, + { + "id": "run-4f500072-2e25-4cf5-b53c-231b744da550-0", + "type": "ai", + "content": "1383.5745", + "example": false, + "tool_calls": [], + "usage_metadata": { + "input_tokens": 1344, + "total_tokens": 1353, + "output_tokens": 9 + }, + "additional_kwargs": {}, + "response_metadata": { + "id": "msg_01H19ivooTk6dXCEwF54txmt", + "model": "claude-3-sonnet-20240229", + "usage": { + "input_tokens": 1344, + "output_tokens": 9 + }, + "stop_reason": "end_turn", + "stop_sequence": null + }, + "invalid_tool_calls": [] + } + ] + } + }, + { + "id": "415dd80f-c6fe-4350-aec3-ca35fbb8bafa", + "created_at": "2024-07-09T19:37:14.641556+00:00", + "modified_at": "2024-08-15T18:01:14.492779+00:00", + "name": "#415d @ multiverse-math-examples-for-few-shot", + "dataset_id": "d64fd4c5-5642-40fe-85d1-d757467415b6", + "source_run_id": "3e207d90-6880-4496-ba67-093b41d2bf3d", + "metadata": { + "dataset_split": [ + "base" + ] + }, + "inputs": { + "input": "evaluate the negation of -100", + "system": false + }, + "outputs": { + "output": [ + { + "id": "43d1f620-1f59-46f5-b99b-96efc5bfebd8", + "type": "system", + "content": "You are requested to solve math questions in an alternate mathematical universe. The operations have been altered to yield different results than expected. Do not guess the answer or rely on your innate knowledge of math. Use the provided tools to answer the question. While associativity and commutativity apply, distributivity does not. Answer the question using the fewest possible tools. Your final answer should include the numeric response on its own without any clarifications. That means if the answer is 5, you should return \"5\" and not \"The answer is 5\" or \"So the answer to your question is 5\".", + "additional_kwargs": {}, + "response_metadata": {} + }, + { + "id": "df7b244e-98f8-48ea-8e43-cdd7b283bcd3", + "type": "human", + "content": "evaluate the negation of -100", + "example": false, + "additional_kwargs": {}, + "response_metadata": {} + }, + { + "id": "run-f2dcb8ec-45ff-4920-af49-b5a7bfa6a3cc-0", + "type": "ai", + "content": [ + { + "id": "toolu_01HTpq4cYNUac6F7omUc2Wz3", + "name": "negate", + "type": "tool_use", + "input": { + "a": -100 + } + } + ], + "example": false, + "tool_calls": [ + { + "id": "toolu_01HTpq4cYNUac6F7omUc2Wz3", + "args": { + "a": -100 + }, + "name": "negate" + } + ], + "usage_metadata": { + "input_tokens": 920, + "total_tokens": 973, + "output_tokens": 53 + }, + "additional_kwargs": {}, + "response_metadata": { + "id": "msg_01YUXV8e7RLcYf7FLTStX68D", + "model": "claude-3-sonnet-20240229", + "usage": { + "input_tokens": 920, + "output_tokens": 53 + }, + "stop_reason": "tool_use", + "stop_sequence": null + }, + "invalid_tool_calls": [] + }, + { + "id": "492a7f3f-0464-4312-9cfb-7671943e4065", + "name": "negate", + "type": "tool", + "content": "-100.0", + "tool_call_id": "toolu_01HTpq4cYNUac6F7omUc2Wz3", + "additional_kwargs": {}, + "response_metadata": {} + }, + { + "id": "run-88edae55-abb3-4d2c-b096-a85e45ee04b6-0", + "type": "ai", + "content": "So the answer is 100.", + "example": false, + "tool_calls": [], + "usage_metadata": { + "input_tokens": 989, + "total_tokens": 1000, + "output_tokens": 11 + }, + "additional_kwargs": {}, + "response_metadata": { + "id": "msg_01VPw9da9nFRby75Y937YMnE", + "model": "claude-3-sonnet-20240229", + "usage": { + "input_tokens": 989, + "output_tokens": 11 + }, + "stop_reason": "end_turn", + "stop_sequence": null + }, + "invalid_tool_calls": [] + }, + { + "id": "964f806e-44a9-46d5-9805-8f367fe7f141", + "type": "human", + "content": "100 is incorrect. Please refer to the output of your tool call.", + "example": false, + "additional_kwargs": {}, + "response_metadata": {} + }, + { + "id": "run-e6c73250-c5b2-4805-83af-0b7cea1aba2b-0", + "type": "ai", + "content": [ + { + "text": "You're right, my previous answer was incorrect. Let me re-evaluate using the tool output:", + "type": "text" + }, + { + "id": "toolu_01XsJQboYghGDygQpPjJkeRq", + "name": "negate", + "type": "tool_use", + "input": { + "a": -100 + } + } + ], + "example": false, + "tool_calls": [ + { + "id": "toolu_01XsJQboYghGDygQpPjJkeRq", + "args": { + "a": -100 + }, + "name": "negate" + } + ], + "usage_metadata": { + "input_tokens": 1019, + "total_tokens": 1093, + "output_tokens": 74 + }, + "additional_kwargs": {}, + "response_metadata": { + "id": "msg_01287oV1ftb1R2JJKoDjyWvA", + "model": "claude-3-sonnet-20240229", + "usage": { + "input_tokens": 1019, + "output_tokens": 74 + }, + "stop_reason": "tool_use", + "stop_sequence": null + }, + "invalid_tool_calls": [] + }, + { + "id": "fbf42cde-f004-41a7-99f1-5641acd8a083", + "name": "negate", + "type": "tool", + "content": "-100.0", + "tool_call_id": "toolu_01XsJQboYghGDygQpPjJkeRq", + "additional_kwargs": {}, + "response_metadata": {} + }, + { + "id": "run-a6bcb848-6966-4bbf-919a-ded95e79dd91-0", + "type": "ai", + "content": "The answer is -100.0", + "example": false, + "tool_calls": [], + "usage_metadata": { + "input_tokens": 1108, + "total_tokens": 1119, + "output_tokens": 11 + }, + "additional_kwargs": {}, + "response_metadata": { + "id": "msg_016bFgPZvrVEcN6RDyEbVtzz", + "model": "claude-3-sonnet-20240229", + "usage": { + "input_tokens": 1108, + "output_tokens": 11 + }, + "stop_reason": "end_turn", + "stop_sequence": null + }, + "invalid_tool_calls": [] + }, + { + "id": "63c94d84-815d-4b56-8c3a-ecc947869822", + "type": "human", + "content": "You have the correct numerical answer but are returning additional text. Please only respond with the numerical answer.", + "example": false, + "additional_kwargs": {}, + "response_metadata": {} + }, + { + "id": "run-eda696da-ad0f-4454-b0fc-00bccb2cbee6-0", + "type": "ai", + "content": "-100.0", + "example": false, + "tool_calls": [], + "usage_metadata": { + "input_tokens": 1142, + "total_tokens": 1149, + "output_tokens": 7 + }, + "additional_kwargs": {}, + "response_metadata": { + "id": "msg_019zQeQSyTfNaGfYKqMtVYbP", + "model": "claude-3-sonnet-20240229", + "usage": { + "input_tokens": 1142, + "output_tokens": 7 + }, + "stop_reason": "end_turn", + "stop_sequence": null + }, + "invalid_tool_calls": [] + } + ] + } + }, + { + "id": "23b6ef25-b5fa-4399-9682-b08d05ae9688", + "created_at": "2024-07-09T19:21:07.142926+00:00", + "modified_at": "2024-07-09T19:21:07.142926+00:00", + "name": "#23b6 @ multiverse-math-examples-for-few-shot", + "dataset_id": "d64fd4c5-5642-40fe-85d1-d757467415b6", + "source_run_id": "2ce994c9-cc39-4d8b-a145-7f23fd03b945", + "metadata": { + "dataset_split": [ + "base" + ] + }, + "inputs": { + "input": "if one gazoink is 4 badoinks, each of which is 6 foos, each of wich is 3 bars - how many bars in 3 gazoinks?", + "system": false + }, + "outputs": { + "output": [ + { + "id": "cbe7ed83-86e1-4e46-89de-6646f8b55cef", + "type": "system", + "content": "You are requested to solve math questions in an alternate mathematical universe. The operations have been altered to yield different results than expected. Do not guess the answer or rely on your innate knowledge of math. Use the provided tools to answer the question. While associativity and commutativity apply, distributivity does not. Answer the question using the fewest possible tools. Only include the numeric response without any clarifications.", + "additional_kwargs": {}, + "response_metadata": {} + }, + { + "id": "04946246-09a8-4465-be95-037efd7dae55", + "type": "human", + "content": "if one gazoink is 4 badoinks, each of which is 6 foos, each of wich is 3 bars - how many bars in 3 gazoinks?", + "example": false, + "additional_kwargs": {}, + "response_metadata": {} + }, + { + "id": "run-d6f0954e-b21b-4ea8-ad98-0ee64cfc824e-0", + "type": "ai", + "content": [ + { + "id": "toolu_016RjRHSEyDZRqKhGrb8uvjJ", + "name": "multiply", + "type": "tool_use", + "input": { + "a": 3, + "b": 4 + } + } + ], + "example": false, + "tool_calls": [ + { + "id": "toolu_016RjRHSEyDZRqKhGrb8uvjJ", + "args": { + "a": 3, + "b": 4 + }, + "name": "multiply" + } + ], + "usage_metadata": { + "input_tokens": 916, + "total_tokens": 984, + "output_tokens": 68 + }, + "additional_kwargs": {}, + "response_metadata": { + "id": "msg_01MBWxgouUBzomwTvXhomGVq", + "model": "claude-3-sonnet-20240229", + "usage": { + "input_tokens": 916, + "output_tokens": 68 + }, + "stop_reason": "tool_use", + "stop_sequence": null + }, + "invalid_tool_calls": [] + }, + { + "id": "3d4c72c4-f009-48ce-b739-1d3f28ee4803", + "name": "multiply", + "type": "tool", + "content": "13.2", + "tool_call_id": "toolu_016RjRHSEyDZRqKhGrb8uvjJ", + "additional_kwargs": {}, + "response_metadata": {} + }, + { + "id": "run-26dd7e83-f5fb-4c70-8ba1-271300ffeb25-0", + "type": "ai", + "content": [ + { + "id": "toolu_01PqvszxiuXrVJ9bwgTWaH3q", + "name": "multiply", + "type": "tool_use", + "input": { + "a": 13.2, + "b": 6 + } + } + ], + "example": false, + "tool_calls": [ + { + "id": "toolu_01PqvszxiuXrVJ9bwgTWaH3q", + "args": { + "a": 13.2, + "b": 6 + }, + "name": "multiply" + } + ], + "usage_metadata": { + "input_tokens": 999, + "total_tokens": 1070, + "output_tokens": 71 + }, + "additional_kwargs": {}, + "response_metadata": { + "id": "msg_01VTFvtCxtR3rN58hCmjt2oH", + "model": "claude-3-sonnet-20240229", + "usage": { + "input_tokens": 999, + "output_tokens": 71 + }, + "stop_reason": "tool_use", + "stop_sequence": null + }, + "invalid_tool_calls": [] + }, + { + "id": "ca4e0317-7b3a-4638-933c-1efd98bc4fda", + "name": "multiply", + "type": "tool", + "content": "87.12", + "tool_call_id": "toolu_01PqvszxiuXrVJ9bwgTWaH3q", + "additional_kwargs": {}, + "response_metadata": {} + }, + { + "id": "run-007794ac-3590-4b9e-b678-008f02e40042-0", + "type": "ai", + "content": [ + { + "id": "toolu_01LU3RqRUXZRLRoJ2AZNmPed", + "name": "multiply", + "type": "tool_use", + "input": { + "a": 87.12, + "b": 3 + } + } + ], + "example": false, + "tool_calls": [ + { + "id": "toolu_01LU3RqRUXZRLRoJ2AZNmPed", + "args": { + "a": 87.12, + "b": 3 + }, + "name": "multiply" + } + ], + "usage_metadata": { + "input_tokens": 1084, + "total_tokens": 1155, + "output_tokens": 71 + }, + "additional_kwargs": {}, + "response_metadata": { + "id": "msg_017BEkSqmTsmtJaTxAzfRMEh", + "model": "claude-3-sonnet-20240229", + "usage": { + "input_tokens": 1084, + "output_tokens": 71 + }, + "stop_reason": "tool_use", + "stop_sequence": null + }, + "invalid_tool_calls": [] + }, + { + "id": "7f58c121-6f21-4c7b-ba38-aa820e274ff8", + "name": "multiply", + "type": "tool", + "content": "287.496", + "tool_call_id": "toolu_01LU3RqRUXZRLRoJ2AZNmPed", + "additional_kwargs": {}, + "response_metadata": {} + }, + { + "id": "run-51e35afb-7ec6-4738-93e2-92f80b5c9377-0", + "type": "ai", + "content": "287.496", + "example": false, + "tool_calls": [], + "usage_metadata": { + "input_tokens": 1169, + "total_tokens": 1176, + "output_tokens": 7 + }, + "additional_kwargs": {}, + "response_metadata": { + "id": "msg_01Tx9kSNapSg8aUbWZXiS1NL", + "model": "claude-3-sonnet-20240229", + "usage": { + "input_tokens": 1169, + "output_tokens": 7 + }, + "stop_reason": "end_turn", + "stop_sequence": null + }, + "invalid_tool_calls": [] + } + ] + } + }, + { + "id": "f3a5a482-910b-402a-8b49-31dbd38dba69", + "created_at": "2024-07-09T18:31:22.954376+00:00", + "modified_at": "2024-07-09T18:31:22.954376+00:00", + "name": "#f3a5 @ multiverse-math-examples-for-few-shot", + "dataset_id": "d64fd4c5-5642-40fe-85d1-d757467415b6", + "source_run_id": "e313510f-5155-48fb-a1af-8ec998cd7dbd", + "metadata": { + "dataset_split": [ + "base" + ] + }, + "inputs": { + "input": "What is the value of tangent of 15 degrees?", + "system": false + }, + "outputs": { + "output": [ + { + "id": "e3f864e9-10e4-402e-9a2a-04680152146e", + "type": "system", + "content": "You are requested to solve math questions in an alternate mathematical universe. The operations have been altered to yield different results than expected. Do not guess the answer or rely on your innate knowledge of math. Use the provided tools to answer the question. While associativity and commutativity apply, distributivity does not. Answer the question using the fewest possible tools. Your final answer should include the numeric response on its own without any clarifications. That means if the answer is 5, you should return \"5\" and not \"The answer is 5\" or \"So the answer to your question is 5\".", + "additional_kwargs": {}, + "response_metadata": {} + }, + { + "id": "88c4febe-5445-4f91-937e-caa8760cec19", + "type": "human", + "content": "What is the value of tangent of 15 degrees?", + "example": false, + "additional_kwargs": {}, + "response_metadata": {} + }, + { + "id": "run-9a440c86-f7f9-4219-b6bc-a35d64bbfc38-0", + "type": "ai", + "content": [ + { + "text": "Here is how to calculate the tangent of 15 degrees using the provided tools:", + "type": "text" + }, + { + "id": "toolu_01M9AvVXTPvFWLiKni8Sh1T4", + "name": "pi", + "type": "tool_use", + "input": {} + } + ], + "example": false, + "tool_calls": [ + { + "id": "toolu_01M9AvVXTPvFWLiKni8Sh1T4", + "args": {}, + "name": "pi" + } + ], + "usage_metadata": { + "input_tokens": 926, + "total_tokens": 979, + "output_tokens": 53 + }, + "additional_kwargs": {}, + "response_metadata": { + "id": "msg_01Uk5q5uxL5Y5YGt5BQMwVEU", + "model": "claude-3-sonnet-20240229", + "usage": { + "input_tokens": 926, + "output_tokens": 53 + }, + "stop_reason": "tool_use", + "stop_sequence": null + }, + "invalid_tool_calls": [] + }, + { + "id": "39667772-2baf-4e9e-97ae-961e3670a474", + "name": "pi", + "type": "tool", + "content": "2.718281828459045", + "tool_call_id": "toolu_01M9AvVXTPvFWLiKni8Sh1T4", + "additional_kwargs": {}, + "response_metadata": {} + }, + { + "id": "run-940b9125-591b-41f3-bbd8-6b474953bbd0-0", + "type": "ai", + "content": [ + { + "id": "toolu_01JGvtXdtQtLmk7LwERkqDKH", + "name": "divide", + "type": "tool_use", + "input": { + "a": 15, + "b": 180 + } + } + ], + "example": false, + "tool_calls": [ + { + "id": "toolu_01JGvtXdtQtLmk7LwERkqDKH", + "args": { + "a": 15, + "b": 180 + }, + "name": "divide" + } + ], + "usage_metadata": { + "input_tokens": 997, + "total_tokens": 1066, + "output_tokens": 69 + }, + "additional_kwargs": {}, + "response_metadata": { + "id": "msg_01HwYrEuUKLeZ9WHF8k4XpEm", + "model": "claude-3-sonnet-20240229", + "usage": { + "input_tokens": 997, + "output_tokens": 69 + }, + "stop_reason": "tool_use", + "stop_sequence": null + }, + "invalid_tool_calls": [] + }, + { + "id": "003a2745-230c-4e1a-80b3-62aca06d40ae", + "name": "divide", + "type": "tool", + "content": "0.041666666666666664", + "tool_call_id": "toolu_01JGvtXdtQtLmk7LwERkqDKH", + "additional_kwargs": {}, + "response_metadata": {} + }, + { + "id": "run-e3881f88-26ef-4710-b79d-ec87cedc9b3f-0", + "type": "ai", + "content": [ + { + "id": "toolu_012jdaKjUq4Up8EJF7PtbRnU", + "name": "multiply", + "type": "tool_use", + "input": { + "a": 0.041666666666666664, + "b": 2.718281828459045 + } + } + ], + "example": false, + "tool_calls": [ + { + "id": "toolu_012jdaKjUq4Up8EJF7PtbRnU", + "args": { + "a": 0.041666666666666664, + "b": 2.718281828459045 + }, + "name": "multiply" + } + ], + "usage_metadata": { + "input_tokens": 1085, + "total_tokens": 1167, + "output_tokens": 82 + }, + "additional_kwargs": {}, + "response_metadata": { + "id": "msg_014gPQ75pqPgox2gDgB5kGiW", + "model": "claude-3-sonnet-20240229", + "usage": { + "input_tokens": 1085, + "output_tokens": 82 + }, + "stop_reason": "tool_use", + "stop_sequence": null + }, + "invalid_tool_calls": [] + }, + { + "id": "13c27c72-71d6-4763-843c-9f8ac1fed43f", + "name": "multiply", + "type": "tool", + "content": "0.12458791713770624", + "tool_call_id": "toolu_012jdaKjUq4Up8EJF7PtbRnU", + "additional_kwargs": {}, + "response_metadata": {} + }, + { + "id": "run-913fb3f0-1642-489f-b6af-486ddfb7e599-0", + "type": "ai", + "content": [ + { + "id": "toolu_013GcHeUTjjcPm8zyrUb8y7K", + "name": "sin", + "type": "tool_use", + "input": { + "radians": 0.12458791713770624 + } + } + ], + "example": false, + "tool_calls": [ + { + "id": "toolu_013GcHeUTjjcPm8zyrUb8y7K", + "args": { + "radians": 0.12458791713770624 + }, + "name": "sin" + } + ], + "usage_metadata": { + "input_tokens": 1186, + "total_tokens": 1246, + "output_tokens": 60 + }, + "additional_kwargs": {}, + "response_metadata": { + "id": "msg_01JagZM5K2BZy3gqbVqNvyfh", + "model": "claude-3-sonnet-20240229", + "usage": { + "input_tokens": 1186, + "output_tokens": 60 + }, + "stop_reason": "tool_use", + "stop_sequence": null + }, + "invalid_tool_calls": [] + }, + { + "id": "80035591-053a-41c7-a780-6ef2accb55bd", + "name": "sin", + "type": "tool", + "content": "0.9922489593051885", + "tool_call_id": "toolu_013GcHeUTjjcPm8zyrUb8y7K", + "additional_kwargs": {}, + "response_metadata": {} + }, + { + "id": "run-e8187305-6596-43ba-b53e-fdc3c4bc949c-0", + "type": "ai", + "content": [ + { + "id": "toolu_01H8VQ4CY79YWEv7UbNCq6PU", + "name": "cos", + "type": "tool_use", + "input": { + "radians": 0.12458791713770624 + } + } + ], + "example": false, + "tool_calls": [ + { + "id": "toolu_01H8VQ4CY79YWEv7UbNCq6PU", + "args": { + "radians": 0.12458791713770624 + }, + "name": "cos" + } + ], + "usage_metadata": { + "input_tokens": 1265, + "total_tokens": 1325, + "output_tokens": 60 + }, + "additional_kwargs": {}, + "response_metadata": { + "id": "msg_01BxSVNvzVcLqgw8w9r2TdCy", + "model": "claude-3-sonnet-20240229", + "usage": { + "input_tokens": 1265, + "output_tokens": 60 + }, + "stop_reason": "tool_use", + "stop_sequence": null + }, + "invalid_tool_calls": [] + }, + { + "id": "d0bd5b2c-5f3c-453c-ba68-6905a07d1505", + "name": "cos", + "type": "tool", + "content": "0.12426585515647588", + "tool_call_id": "toolu_01H8VQ4CY79YWEv7UbNCq6PU", + "additional_kwargs": {}, + "response_metadata": {} + }, + { + "id": "run-e1452ef5-35c0-4f6e-a73f-838aa5a1d618-0", + "type": "ai", + "content": [ + { + "id": "toolu_01RAiD2JXsJXj3AHcodeYyoU", + "name": "divide", + "type": "tool_use", + "input": { + "a": 0.9922489593051885, + "b": 0.12426585515647588 + } + } + ], + "example": false, + "tool_calls": [ + { + "id": "toolu_01RAiD2JXsJXj3AHcodeYyoU", + "args": { + "a": 0.9922489593051885, + "b": 0.12426585515647588 + }, + "name": "divide" + } + ], + "usage_metadata": { + "input_tokens": 1344, + "total_tokens": 1427, + "output_tokens": 83 + }, + "additional_kwargs": {}, + "response_metadata": { + "id": "msg_01TWTmVU2N9QSCokuFqZsTsH", + "model": "claude-3-sonnet-20240229", + "usage": { + "input_tokens": 1344, + "output_tokens": 83 + }, + "stop_reason": "tool_use", + "stop_sequence": null + }, + "invalid_tool_calls": [] + }, + { + "id": "7c5b39e3-b824-4fb0-8522-4e218cc595ca", + "name": "divide", + "type": "tool", + "content": "3.9924440951850615", + "tool_call_id": "toolu_01RAiD2JXsJXj3AHcodeYyoU", + "additional_kwargs": {}, + "response_metadata": {} + }, + { + "id": "run-a3c4b10f-4235-43d8-8d0c-d722d287c99e-0", + "type": "ai", + "content": "3.9924440951850615", + "example": false, + "tool_calls": [], + "usage_metadata": { + "input_tokens": 1446, + "total_tokens": 1458, + "output_tokens": 12 + }, + "additional_kwargs": {}, + "response_metadata": { + "id": "msg_01VzZDqJPcWgwq8tGe8ktHjN", + "model": "claude-3-sonnet-20240229", + "usage": { + "input_tokens": 1446, + "output_tokens": 12 + }, + "stop_reason": "end_turn", + "stop_sequence": null + }, + "invalid_tool_calls": [] + } + ] + } + }, + { + "id": "a738cf19-6e5e-43d8-88b0-51a9f2f7bb32", + "created_at": "2024-07-09T18:00:05.951776+00:00", + "modified_at": "2024-07-09T18:00:05.951776+00:00", + "name": "#a738 @ multiverse-math-examples-for-few-shot", + "dataset_id": "d64fd4c5-5642-40fe-85d1-d757467415b6", + "source_run_id": "08825d00-6d61-4ce9-adae-dd9181dac5dc", + "metadata": { + "dataset_split": [ + "base" + ] + }, + "inputs": { + "input": "For the first 5 days of the week I ran 45 minutes each day, and on the weekend I did one 100 minute run. How many minutes did I spend running this week?", + "system": false + }, + "outputs": { + "output": [ + { + "id": "39cbdd34-e80d-4cee-b64d-a08b332960de", + "type": "system", + "content": "You are requested to solve math questions in an alternate mathematical universe. The operations have been altered to yield different results than expected. Do not guess the answer or rely on your innate knowledge of math. Use the provided tools to answer the question. While associativity and commutativity apply, distributivity does not. Answer the question using the fewest possible tools. Your final answer should include the numeric response on its own without any clarifications. Please return a non-empty answer.", + "additional_kwargs": {}, + "response_metadata": {} + }, + { + "id": "88e60ba2-4976-40ab-9cc1-83017f33c36d", + "type": "human", + "content": "For the first 5 days of the week I ran 45 minutes each day, and on the weekend I did one 100 minute run. How many minutes did I spend running this week?", + "example": false, + "additional_kwargs": {}, + "response_metadata": {} + }, + { + "id": "run-26185a0d-97a3-4e5f-baaf-c0c5cdb8624f-0", + "type": "ai", + "content": [ + { + "id": "toolu_01XNBmZD1wHJJZQJ6GddFnxZ", + "name": "multiply", + "type": "tool_use", + "input": { + "a": 5, + "b": 45 + } + } + ], + "example": false, + "tool_calls": [ + { + "id": "toolu_01XNBmZD1wHJJZQJ6GddFnxZ", + "args": { + "a": 5, + "b": 45 + }, + "name": "multiply" + } + ], + "usage_metadata": { + "input_tokens": 928, + "total_tokens": 996, + "output_tokens": 68 + }, + "additional_kwargs": {}, + "response_metadata": { + "id": "msg_01RwjPkwEam2UnV8r6mP4fVd", + "model": "claude-3-sonnet-20240229", + "usage": { + "input_tokens": 928, + "output_tokens": 68 + }, + "stop_reason": "tool_use", + "stop_sequence": null + }, + "invalid_tool_calls": [] + }, + { + "id": "c9769517-674a-434f-9364-abfb267cd83c", + "name": "multiply", + "type": "tool", + "content": "247.5", + "tool_call_id": "toolu_01XNBmZD1wHJJZQJ6GddFnxZ", + "additional_kwargs": {}, + "response_metadata": {} + }, + { + "id": "run-b7870fa1-5427-4199-b270-59d6150da667-0", + "type": "ai", + "content": [ + { + "id": "toolu_01BS72aUeWQzbkzJjQwrkx7A", + "name": "add", + "type": "tool_use", + "input": { + "a": 247.5, + "b": 100 + } + } + ], + "example": false, + "tool_calls": [ + { + "id": "toolu_01BS72aUeWQzbkzJjQwrkx7A", + "args": { + "a": 247.5, + "b": 100 + }, + "name": "add" + } + ], + "usage_metadata": { + "input_tokens": 1011, + "total_tokens": 1082, + "output_tokens": 71 + }, + "additional_kwargs": {}, + "response_metadata": { + "id": "msg_015h6Re3HMYfDoLJ9QVZftaY", + "model": "claude-3-sonnet-20240229", + "usage": { + "input_tokens": 1011, + "output_tokens": 71 + }, + "stop_reason": "tool_use", + "stop_sequence": null + }, + "invalid_tool_calls": [] + }, + { + "id": "250f500d-bbd5-4191-8e2a-f77435841778", + "name": "add", + "type": "tool", + "content": "348.7", + "tool_call_id": "toolu_01BS72aUeWQzbkzJjQwrkx7A", + "additional_kwargs": {}, + "response_metadata": {} + }, + { + "id": "run-fa7b9bb3-533c-44b8-9e34-35af1286c0a6-0", + "type": "ai", + "content": "348.7", + "example": false, + "tool_calls": [], + "usage_metadata": { + "input_tokens": 1096, + "total_tokens": 1103, + "output_tokens": 7 + }, + "additional_kwargs": {}, + "response_metadata": { + "id": "msg_0145tzF34B7pZBrDCv6FtzWs", + "model": "claude-3-sonnet-20240229", + "usage": { + "input_tokens": 1096, + "output_tokens": 7 + }, + "stop_reason": "end_turn", + "stop_sequence": null + }, + "invalid_tool_calls": [] + } + ] + } + }, + { + "id": "5a6536b6-55ad-45ee-b3e1-ba5f0a89fc03", + "created_at": "2024-07-08T23:26:34.541129+00:00", + "modified_at": "2024-07-08T23:26:34.541129+00:00", + "name": "#5a65 @ multiverse-math-examples-for-few-shot", + "dataset_id": "d64fd4c5-5642-40fe-85d1-d757467415b6", + "source_run_id": "6e4eb059-d73d-4b8b-9b04-4a809e374139", + "metadata": { + "dataset_split": [ + "base" + ] + }, + "inputs": { + "input": "Negate the value of 2 subtracted from 5", + "system": false + }, + "outputs": { + "output": [ + { + "id": "c01e7746-a71b-4d7c-af5e-9523646ba964", + "type": "system", + "content": "You are requested to solve math questions in an alternate mathematical universe. The operations have been altered to yield different results than expected. Do not guess the answer or rely on your innate knowledge of math. Use the provided tools to answer the question. While associativity and commutativity apply, distributivity does not. Answer the question using the fewest possible tools. Your final answer should include the numeric response on its own without any clarifications. Please return a non-empty answer.", + "additional_kwargs": {}, + "response_metadata": {} + }, + { + "id": "48344924-81ec-4987-80d4-c94902e81852", + "type": "human", + "content": "Negate the value of 2 subtracted from 5", + "example": false, + "additional_kwargs": {}, + "response_metadata": {} + }, + { + "id": "run-f3dd2c24-8ae5-4143-8ad3-3075ba432c17-0", + "type": "ai", + "content": [ + { + "id": "toolu_01Qvk84UL1MMQC7UMLXjDEoL", + "name": "subtract", + "type": "tool_use", + "input": { + "a": 5, + "b": 2 + } + } + ], + "example": false, + "tool_calls": [ + { + "id": "toolu_01Qvk84UL1MMQC7UMLXjDEoL", + "args": { + "a": 5, + "b": 2 + }, + "name": "subtract" + } + ], + "usage_metadata": { + "input_tokens": 900, + "total_tokens": 968, + "output_tokens": 68 + }, + "additional_kwargs": {}, + "response_metadata": { + "id": "msg_01HA93WmfUr1pGKGwh25pNvs", + "model": "claude-3-sonnet-20240229", + "usage": { + "input_tokens": 900, + "output_tokens": 68 + }, + "stop_reason": "tool_use", + "stop_sequence": null + }, + "invalid_tool_calls": [] + }, + { + "id": "6cbd94cc-083d-4756-bb63-703c68af36cd", + "name": "subtract", + "type": "tool", + "content": "0.0", + "tool_call_id": "toolu_01Qvk84UL1MMQC7UMLXjDEoL", + "additional_kwargs": {}, + "response_metadata": {} + }, + { + "id": "run-97a2b647-4141-4285-b1ec-e30ced74fbc5-0", + "type": "ai", + "content": [ + { + "id": "toolu_01J6Nw9Nej1QUtvPchRvBoks", + "name": "negate", + "type": "tool_use", + "input": { + "a": 0 + } + } + ], + "example": false, + "tool_calls": [ + { + "id": "toolu_01J6Nw9Nej1QUtvPchRvBoks", + "args": { + "a": 0 + }, + "name": "negate" + } + ], + "usage_metadata": { + "input_tokens": 983, + "total_tokens": 1038, + "output_tokens": 55 + }, + "additional_kwargs": {}, + "response_metadata": { + "id": "msg_016G7JgaK9BEbDZtr7Tpyx2C", + "model": "claude-3-sonnet-20240229", + "usage": { + "input_tokens": 983, + "output_tokens": 55 + }, + "stop_reason": "tool_use", + "stop_sequence": null + }, + "invalid_tool_calls": [] + }, + { + "id": "064bd7ef-d6e1-4ae1-b7cd-5d879391701a", + "name": "negate", + "type": "tool", + "content": "0.0", + "tool_call_id": "toolu_01J6Nw9Nej1QUtvPchRvBoks", + "additional_kwargs": {}, + "response_metadata": {} + }, + { + "id": "run-5982b347-0e73-4e91-9cf4-65a03b918b65-0", + "type": "ai", + "content": "0.0", + "example": false, + "tool_calls": [], + "usage_metadata": { + "input_tokens": 1052, + "total_tokens": 1059, + "output_tokens": 7 + }, + "additional_kwargs": {}, + "response_metadata": { + "id": "msg_01QbDwVuMjk4gRzZjv3xUqeU", + "model": "claude-3-sonnet-20240229", + "usage": { + "input_tokens": 1052, + "output_tokens": 7 + }, + "stop_reason": "end_turn", + "stop_sequence": null + }, + "invalid_tool_calls": [] + } + ] + } + } +] \ No newline at end of file diff --git a/docs/core_docs/docs/how_to/example_selectors_langsmith.ipynb b/docs/core_docs/docs/how_to/example_selectors_langsmith.ipynb new file mode 100644 index 000000000000..3a145a257ed0 --- /dev/null +++ b/docs/core_docs/docs/how_to/example_selectors_langsmith.ipynb @@ -0,0 +1,553 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "4f7e423b", + "metadata": {}, + "source": [ + "# How to select examples from a LangSmith dataset\n", + "\n", + "```{=mdx}\n", + "\n", + ":::tip Prerequisites\n", + "\n", + "- [Chat models](/docs/concepts/#chat-models)\n", + "- [Few-shot-prompting](/docs/concepts/#few-shot-prompting)\n", + "- [LangSmith](/docs/concepts/#langsmith)\n", + "\n", + ":::\n", + "\n", + "\n", + ":::note Compatibility\n", + "\n", + "- `langsmith` >= 0.1.43\n", + "\n", + ":::\n", + "\n", + "```\n", + "\n", + "LangSmith datasets have built-in support for similarity search, making them a great tool for building and querying few-shot examples.\n", + "\n", + "In this guide we'll see how to use an indexed LangSmith dataset as a few-shot example selector.\n", + "\n", + "## Setup\n", + "\n", + "Before getting started make sure you've [created a LangSmith account](https://smith.langchain.com/) and set your credentials:\n", + "\n", + "```typescript\n", + "process.env.LANGSMITH_API_KEY=\"your-api-key\"\n", + "process.env.LANGSMITH_TRACING=\"true\"\n", + "```\n", + "\n", + "We'll need to install the `langsmith` SDK. In this example we'll also make use of `langchain` and `@langchain/anthropic`:\n", + "\n", + "```{=mdx}\n", + "\n", + "import Npm2Yarn from \"@theme/Npm2Yarn\"\n", + "\n", + "\n", + " langsmith langchain @langchain/anthropic zod zod-to-json-schema\n", + "\n", + "\n", + "```" + ] + }, + { + "cell_type": "markdown", + "id": "fc716e12", + "metadata": {}, + "source": [ + "Now we'll clone a public dataset and turn on indexing for the dataset. We can also turn on indexing via the [LangSmith UI](https://docs.smith.langchain.com/how_to_guides/datasets/index_datasets_for_dynamic_few_shot_example_selection).\n", + "\n", + "We'll create a clone the [Multiverse math few shot example dataset](https://blog.langchain.dev/few-shot-prompting-to-improve-tool-calling-performance/).\n", + "\n", + "This enables searching over the dataset, and will make sure that anytime we update/add examples they are also indexed.\n", + "\n", + "The first step to creating a clone is to read the JSON file containing the examples and convert them to the format expected by LangSmith for creating examples:" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "2bcc86a0", + "metadata": {}, + "outputs": [], + "source": [ + "import { Client as LangSmithClient } from 'langsmith';\n", + "import { z } from 'zod';\n", + "import { zodToJsonSchema } from 'zod-to-json-schema';\n", + "import fs from \"fs/promises\";\n", + "\n", + "// Read the example dataset and convert to the format expected by the LangSmith API\n", + "// for creating new examples\n", + "const examplesJson = JSON.parse(\n", + " await fs.readFile(\"../../data/ls_few_shot_example_dataset.json\", \"utf-8\")\n", + ");\n", + "\n", + "let inputs: Record[] = [];\n", + "let outputs: Record[] = [];\n", + "let metadata: Record[] = [];\n", + "\n", + "examplesJson.forEach((ex) => {\n", + " inputs.push(ex.inputs);\n", + " outputs.push(ex.outputs);\n", + " metadata.push(ex.metadata);\n", + "});\n", + "\n", + "// Define our input schema as this is required for indexing\n", + "const inputsSchema = zodToJsonSchema(z.object({\n", + " input: z.string(),\n", + " system: z.boolean().optional(),\n", + "}));\n", + "\n", + "const lsClient = new LangSmithClient();\n", + "\n", + "await lsClient.deleteDataset({ datasetName: \"multiverse-math-examples-for-few-shot-example\" })\n", + "\n", + "const dataset = await lsClient.createDataset(\"multiverse-math-examples-for-few-shot-example\", {\n", + " inputsSchema,\n", + "});\n", + "\n", + "const createdExamples = await lsClient.createExamples({\n", + " inputs,\n", + " outputs,\n", + " metadata,\n", + " datasetId: dataset.id,\n", + "})\n" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "01b5a8f3", + "metadata": {}, + "outputs": [], + "source": [ + "await lsClient.indexDataset({ datasetId: dataset.id });" + ] + }, + { + "cell_type": "markdown", + "id": "5767d171", + "metadata": {}, + "source": [ + "Once the dataset is indexed, we can search for similar examples like so:" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "5013a56f", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "3\n" + ] + } + ], + "source": [ + "const examples = await lsClient.similarExamples(\n", + " { input: \"whats the negation of the negation of the negation of 3\" },\n", + " dataset.id,\n", + " 3,\n", + ")\n", + "console.log(examples.length)" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "a142db06", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "evaluate the negation of -100\n" + ] + } + ], + "source": [ + "console.log(examples[0].inputs.input)" + ] + }, + { + "cell_type": "markdown", + "id": "d2627125", + "metadata": {}, + "source": [ + "For this dataset the outputs are an entire chat history:" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "id": "af5b9191", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[\n", + " {\n", + " id: 'cbe7ed83-86e1-4e46-89de-6646f8b55cef',\n", + " type: 'system',\n", + " content: 'You are requested to solve math questions in an alternate mathematical universe. The operations have been altered to yield different results than expected. Do not guess the answer or rely on your innate knowledge of math. Use the provided tools to answer the question. While associativity and commutativity apply, distributivity does not. Answer the question using the fewest possible tools. Only include the numeric response without any clarifications.',\n", + " additional_kwargs: {},\n", + " response_metadata: {}\n", + " },\n", + " {\n", + " id: '04946246-09a8-4465-be95-037efd7dae55',\n", + " type: 'human',\n", + " content: 'if one gazoink is 4 badoinks, each of which is 6 foos, each of wich is 3 bars - how many bars in 3 gazoinks?',\n", + " example: false,\n", + " additional_kwargs: {},\n", + " response_metadata: {}\n", + " },\n", + " {\n", + " id: 'run-d6f0954e-b21b-4ea8-ad98-0ee64cfc824e-0',\n", + " type: 'ai',\n", + " content: [ [Object] ],\n", + " example: false,\n", + " tool_calls: [ [Object] ],\n", + " usage_metadata: { input_tokens: 916, total_tokens: 984, output_tokens: 68 },\n", + " additional_kwargs: {},\n", + " response_metadata: {\n", + " id: 'msg_01MBWxgouUBzomwTvXhomGVq',\n", + " model: 'claude-3-sonnet-20240229',\n", + " usage: [Object],\n", + " stop_reason: 'tool_use',\n", + " stop_sequence: null\n", + " },\n", + " invalid_tool_calls: []\n", + " },\n", + " {\n", + " id: '3d4c72c4-f009-48ce-b739-1d3f28ee4803',\n", + " name: 'multiply',\n", + " type: 'tool',\n", + " content: '13.2',\n", + " tool_call_id: 'toolu_016RjRHSEyDZRqKhGrb8uvjJ',\n", + " additional_kwargs: {},\n", + " response_metadata: {}\n", + " },\n", + " {\n", + " id: 'run-26dd7e83-f5fb-4c70-8ba1-271300ffeb25-0',\n", + " type: 'ai',\n", + " content: [ [Object] ],\n", + " example: false,\n", + " tool_calls: [ [Object] ],\n", + " usage_metadata: { input_tokens: 999, total_tokens: 1070, output_tokens: 71 },\n", + " additional_kwargs: {},\n", + " response_metadata: {\n", + " id: 'msg_01VTFvtCxtR3rN58hCmjt2oH',\n", + " model: 'claude-3-sonnet-20240229',\n", + " usage: [Object],\n", + " stop_reason: 'tool_use',\n", + " stop_sequence: null\n", + " },\n", + " invalid_tool_calls: []\n", + " },\n", + " {\n", + " id: 'ca4e0317-7b3a-4638-933c-1efd98bc4fda',\n", + " name: 'multiply',\n", + " type: 'tool',\n", + " content: '87.12',\n", + " tool_call_id: 'toolu_01PqvszxiuXrVJ9bwgTWaH3q',\n", + " additional_kwargs: {},\n", + " response_metadata: {}\n", + " },\n", + " {\n", + " id: 'run-007794ac-3590-4b9e-b678-008f02e40042-0',\n", + " type: 'ai',\n", + " content: [ [Object] ],\n", + " example: false,\n", + " tool_calls: [ [Object] ],\n", + " usage_metadata: { input_tokens: 1084, total_tokens: 1155, output_tokens: 71 },\n", + " additional_kwargs: {},\n", + " response_metadata: {\n", + " id: 'msg_017BEkSqmTsmtJaTxAzfRMEh',\n", + " model: 'claude-3-sonnet-20240229',\n", + " usage: [Object],\n", + " stop_reason: 'tool_use',\n", + " stop_sequence: null\n", + " },\n", + " invalid_tool_calls: []\n", + " },\n", + " {\n", + " id: '7f58c121-6f21-4c7b-ba38-aa820e274ff8',\n", + " name: 'multiply',\n", + " type: 'tool',\n", + " content: '287.496',\n", + " tool_call_id: 'toolu_01LU3RqRUXZRLRoJ2AZNmPed',\n", + " additional_kwargs: {},\n", + " response_metadata: {}\n", + " },\n", + " {\n", + " id: 'run-51e35afb-7ec6-4738-93e2-92f80b5c9377-0',\n", + " type: 'ai',\n", + " content: '287.496',\n", + " example: false,\n", + " tool_calls: [],\n", + " usage_metadata: { input_tokens: 1169, total_tokens: 1176, output_tokens: 7 },\n", + " additional_kwargs: {},\n", + " response_metadata: {\n", + " id: 'msg_01Tx9kSNapSg8aUbWZXiS1NL',\n", + " model: 'claude-3-sonnet-20240229',\n", + " usage: [Object],\n", + " stop_reason: 'end_turn',\n", + " stop_sequence: null\n", + " },\n", + " invalid_tool_calls: []\n", + " }\n", + "]\n" + ] + } + ], + "source": [ + "console.log(examples[1].outputs.output)" + ] + }, + { + "cell_type": "markdown", + "id": "e852c8ef", + "metadata": {}, + "source": [ + "The search returns the examples whose inputs are most similar to the query input. We can use this for few-shot prompting a model. The first step is to create a series of math tools we want to allow the model to call:" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "id": "53e03aa1", + "metadata": {}, + "outputs": [], + "source": [ + "import { tool } from '@langchain/core/tools';\n", + "import { z } from 'zod';\n", + "\n", + "const add = tool((input) => {\n", + " return (input.a + input.b).toString();\n", + "}, {\n", + " name: \"add\",\n", + " description: \"Add two numbers\",\n", + " schema: z.object({\n", + " a: z.number().describe(\"The first number to add\"),\n", + " b: z.number().describe(\"The second number to add\"),\n", + " }),\n", + "});\n", + "\n", + "const cos = tool((input) => {\n", + " return Math.cos(input.angle).toString();\n", + "}, {\n", + " name: \"cos\",\n", + " description: \"Calculate the cosine of an angle (in radians)\",\n", + " schema: z.object({\n", + " angle: z.number().describe(\"The angle in radians\"),\n", + " }),\n", + "});\n", + "\n", + "const divide = tool((input) => {\n", + " return (input.a / input.b).toString();\n", + "}, {\n", + " name: \"divide\",\n", + " description: \"Divide two numbers\",\n", + " schema: z.object({\n", + " a: z.number().describe(\"The dividend\"),\n", + " b: z.number().describe(\"The divisor\"),\n", + " }),\n", + "});\n", + "\n", + "const log = tool((input) => {\n", + " return Math.log(input.value).toString();\n", + "}, {\n", + " name: \"log\",\n", + " description: \"Calculate the natural logarithm of a number\",\n", + " schema: z.object({\n", + " value: z.number().describe(\"The number to calculate the logarithm of\"),\n", + " }),\n", + "});\n", + "\n", + "const multiply = tool((input) => {\n", + " return (input.a * input.b).toString();\n", + "}, {\n", + " name: \"multiply\",\n", + " description: \"Multiply two numbers\",\n", + " schema: z.object({\n", + " a: z.number().describe(\"The first number to multiply\"),\n", + " b: z.number().describe(\"The second number to multiply\"),\n", + " }),\n", + "});\n", + "\n", + "const negate = tool((input) => {\n", + " return (-input.a).toString();\n", + "}, {\n", + " name: \"negate\",\n", + " description: \"Negate a number\",\n", + " schema: z.object({\n", + " a: z.number().describe(\"The number to negate\"),\n", + " }),\n", + "});\n", + "\n", + "const pi = tool(() => {\n", + " return Math.PI.toString();\n", + "}, {\n", + " name: \"pi\",\n", + " description: \"Return the value of pi\",\n", + " schema: z.object({}),\n", + "});\n", + "\n", + "const power = tool((input) => {\n", + " return Math.pow(input.base, input.exponent).toString();\n", + "}, {\n", + " name: \"power\",\n", + " description: \"Raise a number to a power\",\n", + " schema: z.object({\n", + " base: z.number().describe(\"The base number\"),\n", + " exponent: z.number().describe(\"The exponent\"),\n", + " }),\n", + "});\n", + "\n", + "const sin = tool((input) => {\n", + " return Math.sin(input.angle).toString();\n", + "}, {\n", + " name: \"sin\",\n", + " description: \"Calculate the sine of an angle (in radians)\",\n", + " schema: z.object({\n", + " angle: z.number().describe(\"The angle in radians\"),\n", + " }),\n", + "});\n", + "\n", + "const subtract = tool((input) => {\n", + " return (input.a - input.b).toString();\n", + "}, {\n", + " name: \"subtract\",\n", + " description: \"Subtract two numbers\",\n", + " schema: z.object({\n", + " a: z.number().describe(\"The number to subtract from\"),\n", + " b: z.number().describe(\"The number to subtract\"),\n", + " }),\n", + "});" + ] + }, + { + "cell_type": "code", + "execution_count": 17, + "id": "12cba1e1", + "metadata": {}, + "outputs": [], + "source": [ + "import { ChatOpenAI } from \"@langchain/openai\";\n", + "import { HumanMessage, SystemMessage, BaseMessage, BaseMessageLike } from \"@langchain/core/messages\";\n", + "import { RunnableLambda } from \"@langchain/core/runnables\";\n", + "import { Client as LangSmithClient, Example } from \"langsmith\";\n", + "import { coerceMessageLikeToMessage } from \"@langchain/core/messages\";\n", + "\n", + "const client = new LangSmithClient();\n", + "\n", + "async function similarExamples(input: Record): Promise> {\n", + " const examples = await client.similarExamples(input, dataset.id, 5);\n", + " return { ...input, examples };\n", + "}\n", + "\n", + "function constructPrompt(input: { examples: Example[], input: string }): BaseMessage[] {\n", + " const instructions = \"You are great at using mathematical tools.\";\n", + " let messages: BaseMessage[] = []\n", + " \n", + " for (const ex of input.examples) {\n", + " // Assuming ex.outputs.output is an array of message-like objects\n", + " messages = messages.concat(ex.outputs.output.flatMap((msg: BaseMessageLike) => coerceMessageLikeToMessage(msg)));\n", + " }\n", + " \n", + " const examples = messages.filter(msg => msg._getType() !== 'system');\n", + " examples.forEach((ex) => {\n", + " if (ex._getType() === 'human') {\n", + " ex.name = \"example_user\";\n", + " } else {\n", + " ex.name = \"example_assistant\";\n", + " }\n", + " });\n", + "\n", + " return [new SystemMessage(instructions), ...examples, new HumanMessage(input.input)];\n", + "}\n", + "\n", + "const llm = new ChatOpenAI({\n", + " model: \"gpt-4o\",\n", + " temperature: 0,\n", + "});\n", + "const tools = [add, cos, divide, log, multiply, negate, pi, power, sin, subtract];\n", + "const llmWithTools = llm.bindTools(tools);\n", + "\n", + "const exampleSelector = new RunnableLambda(\n", + " { func: similarExamples }\n", + ").withConfig({ runName: \"similarExamples\" });\n", + "\n", + "const chain = exampleSelector.pipe(\n", + " new RunnableLambda({\n", + " func: constructPrompt\n", + " }).withConfig({\n", + " runName: \"constructPrompt\"\n", + " })\n", + ").pipe(llmWithTools);" + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "id": "c423b367", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[\n", + " {\n", + " name: 'negate',\n", + " args: { a: 3 },\n", + " type: 'tool_call',\n", + " id: 'call_SX0dmb4AbFu39KkGQDqPXQwa'\n", + " }\n", + "]\n" + ] + } + ], + "source": [ + "const aiMsg = await chain.invoke({ input: \"whats the negation of the negation of 3\", system: false })\n", + "console.log(aiMsg.tool_calls)" + ] + }, + { + "cell_type": "markdown", + "id": "94489b4a", + "metadata": {}, + "source": [ + "Looking at the LangSmith trace, we can see that relevant examples were pulled in in the `similarExamples` step and passed as messages to ChatOpenAI: https://smith.langchain.com/public/20e09618-0746-4973-9382-5b36c3f27083/r." + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "TypeScript", + "language": "typescript", + "name": "tslab" + }, + "language_info": { + "codemirror_mode": { + "mode": "typescript", + "name": "javascript", + "typescript": true + }, + "file_extension": ".ts", + "mimetype": "text/typescript", + "name": "typescript", + "version": "3.7.2" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/examples/package.json b/examples/package.json index 1887b730af06..579c3be83a72 100644 --- a/examples/package.json +++ b/examples/package.json @@ -91,7 +91,7 @@ "ioredis": "^5.3.2", "js-yaml": "^4.1.0", "langchain": "workspace:*", - "langsmith": "^0.1.30", + "langsmith": "^0.1.43", "mongodb": "^6.3.0", "pg": "^8.11.0", "pickleparser": "^0.2.1", diff --git a/langchain-core/package.json b/langchain-core/package.json index aa75a4dadb24..ed901e978c36 100644 --- a/langchain-core/package.json +++ b/langchain-core/package.json @@ -45,7 +45,7 @@ "camelcase": "6", "decamelize": "1.2.0", "js-tiktoken": "^1.0.12", - "langsmith": "~0.1.39", + "langsmith": "^0.1.43", "mustache": "^4.2.0", "p-queue": "^6.6.2", "p-retry": "4", diff --git a/langchain-core/src/messages/utils.ts b/langchain-core/src/messages/utils.ts index 779b9d16eaaf..4a34e03984ee 100644 --- a/langchain-core/src/messages/utils.ts +++ b/langchain-core/src/messages/utils.ts @@ -31,6 +31,13 @@ function _constructMessageFromParams( return new AIMessage(rest); } else if (type === "system") { return new SystemMessage(rest); + } else if (type === "tool" && "tool_call_id" in rest) { + return new ToolMessage({ + ...rest, + content: rest.content, + tool_call_id: rest.tool_call_id as string, + name: rest.name, + }); } else { throw new Error( `Unable to coerce message from array: only human, AI, or system message coercion is currently supported.` diff --git a/yarn.lock b/yarn.lock index 1c74f9d25aab..dd138da859d4 100644 --- a/yarn.lock +++ b/yarn.lock @@ -11711,7 +11711,7 @@ __metadata: jest: ^29.5.0 jest-environment-node: ^29.6.4 js-tiktoken: ^1.0.12 - langsmith: ~0.1.39 + langsmith: ^0.1.43 ml-matrix: ^6.10.4 mustache: ^4.2.0 p-queue: ^6.6.2 @@ -26760,7 +26760,7 @@ __metadata: ioredis: ^5.3.2 js-yaml: ^4.1.0 langchain: "workspace:*" - langsmith: ^0.1.30 + langsmith: ^0.1.43 mongodb: ^6.3.0 pg: ^8.11.0 pickleparser: ^0.2.1 @@ -32362,7 +32362,32 @@ __metadata: languageName: unknown linkType: soft -"langsmith@npm:^0.1.30, langsmith@npm:~0.1.30, langsmith@npm:~0.1.39": +"langsmith@npm:^0.1.43": + version: 0.1.43 + resolution: "langsmith@npm:0.1.43" + dependencies: + "@types/uuid": ^9.0.1 + commander: ^10.0.1 + p-queue: ^6.6.2 + p-retry: 4 + semver: ^7.6.3 + uuid: ^9.0.0 + peerDependencies: + "@langchain/core": "*" + langchain: "*" + openai: "*" + peerDependenciesMeta: + "@langchain/core": + optional: true + langchain: + optional: true + openai: + optional: true + checksum: 83ded542eb9a4f3c7f75671ba9a85b9bb0558251cbc5ad506098068b7c5abcb6062101857cd5e2b324d5eb1e3f4984aff0d08b7b913c3ad6e41d4cbc65492a13 + languageName: node + linkType: hard + +"langsmith@npm:~0.1.30": version: 0.1.39 resolution: "langsmith@npm:0.1.39" dependencies: From 42ad1f495a3642a2be5631999b61dc221a682563 Mon Sep 17 00:00:00 2001 From: Brace Sproul Date: Wed, 21 Aug 2024 11:30:04 -0700 Subject: [PATCH 065/344] core[minor]: Add LangSmith doc loader (#6568) * core[minor]: Add LangSmith doc loader * tests and nits * add test and docs * rename --- .../web_loaders/langsmith.ipynb | 302 ++++++++++++++++++ langchain-core/.gitignore | 4 + langchain-core/langchain.config.js | 1 + langchain-core/package.json | 13 + .../src/document_loaders/langsmith.ts | 182 +++++++++++ .../tests/langsmith.int.test.ts | 67 ++++ 6 files changed, 569 insertions(+) create mode 100644 docs/core_docs/docs/integrations/document_loaders/web_loaders/langsmith.ipynb create mode 100644 langchain-core/src/document_loaders/langsmith.ts create mode 100644 langchain-core/src/document_loaders/tests/langsmith.int.test.ts diff --git a/docs/core_docs/docs/integrations/document_loaders/web_loaders/langsmith.ipynb b/docs/core_docs/docs/integrations/document_loaders/web_loaders/langsmith.ipynb new file mode 100644 index 000000000000..b7e536113685 --- /dev/null +++ b/docs/core_docs/docs/integrations/document_loaders/web_loaders/langsmith.ipynb @@ -0,0 +1,302 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "---\n", + "sidebar_label: FireCrawl\n", + "\n", + "---" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# LangSmithLoader\n", + "\n", + "This notebook provides a quick overview for getting started with the [LangSmithLoader](/docs/integrations/document_loaders/). For detailed documentation of all `LangSmithLoader` features and configurations head to the [API reference](https://api.js.langchain.com/classes/_langchain_core.document_loaders_langsmith.LangSmithLoader.html).\n", + "\n", + "## Overview\n", + "### Integration details\n", + "\n", + "| Class | Package | Local | Serializable | [PY support](https://python.langchain.com/docs/integrations/document_loaders/langsmith)|\n", + "| :--- | :--- | :---: | :---: | :---: |\n", + "| [LangSmithLoader](https://api.js.langchain.com/classes/_langchain_core.document_loaders_langsmith.LangSmithLoader.html) | [@langchain/community](https://api.js.langchain.com/classes/_langchain_core.html) | ✅ | beta | ✅ | \n", + "### Loader features\n", + "| Source | Web Loader | Node Envs Only\n", + "| :---: | :---: | :---: | \n", + "| LangSmithLoader | ✅ | ❌ | \n", + "\n", + "[FireCrawl](https://firecrawl.dev) crawls and convert any website into LLM-ready data. It crawls all accessible sub-pages and give you clean markdown and metadata for each. No sitemap required.\n", + "\n", + "FireCrawl handles complex tasks such as reverse proxies, caching, rate limits, and content blocked by JavaScript. Built by the [mendable.ai](https://mendable.ai) team.\n", + "\n", + "This guide shows how to scrap and crawl entire websites and load them using the `LangSmithLoader` in LangChain.\n", + "\n", + "## Setup\n", + "\n", + "To access the LangSmith document loader you'll need to install `@langchain/core`, create a [LangSmith](https://langsmith.com/) account and get an API key.\n", + "\n", + "### Credentials\n", + "\n", + "Sign up at https://langsmith.com and generate an API key. Once you've done this set the `LANGSMITH_API_KEY` environment variable:\n", + "\n", + "```bash\n", + "export LANGSMITH_API_KEY=\"your-api-key\"\n", + "```\n", + "\n", + "### Installation\n", + "\n", + "The `LangSmithLoader` integration lives in the `@langchain/core` package:\n", + "\n", + "```{=mdx}\n", + "import IntegrationInstallTooltip from \"@mdx_components/integration_install_tooltip.mdx\";\n", + "import Npm2Yarn from \"@theme/Npm2Yarn\";\n", + "\n", + "\n", + "\n", + "\n", + " @langchain/core\n", + "\n", + "\n", + "```" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Create example dataset\n", + "\n", + "For this example, we'll create a new dataset which we'll use in our document loader." + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [], + "source": [ + "import { Client as LangSmithClient } from 'langsmith';\n", + "import { faker } from \"@faker-js/faker\";\n", + "\n", + "const lsClient = new LangSmithClient();\n", + "\n", + "const datasetName = \"LangSmith Few Shot Datasets Notebook\";\n", + "\n", + "const exampleInputs = Array.from({ length: 10 }, (_, i) => ({\n", + " input: faker.lorem.paragraph(),\n", + "}));\n", + "const exampleOutputs = Array.from({ length: 10 }, (_, i) => ({\n", + " output: faker.lorem.sentence(),\n", + "}));\n", + "const exampleMetadata = Array.from({ length: 10 }, (_, i) => ({\n", + " companyCatchPhrase: faker.company.catchPhrase(),\n", + "}));\n", + "\n", + "await lsClient.deleteDataset({\n", + " datasetName,\n", + "})\n", + "\n", + "const dataset = await lsClient.createDataset(datasetName);\n", + "\n", + "const examples = await lsClient.createExamples({\n", + " inputs: exampleInputs,\n", + " outputs: exampleOutputs,\n", + " metadata: exampleMetadata,\n", + " datasetId: dataset.id,\n", + "});" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": {}, + "outputs": [], + "source": [ + "import { LangSmithLoader } from \"@langchain/core/document_loaders/langsmith\"\n", + "\n", + "const loader = new LangSmithLoader({\n", + " datasetName: \"LangSmith Few Shot Datasets Notebook\",\n", + " // Instead of a datasetName, you can alternatively provide a datasetId\n", + " // datasetId: dataset.id,\n", + " contentKey: \"input\",\n", + " limit: 5,\n", + " // formatContent: (content) => content,\n", + " // ... other options\n", + "})" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Load" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "{\n", + " pageContent: 'Conventus supellex aegrotatio termes. Vapulus abscido ubi vita coadunatio modi crapula comparo caecus. Acervus voluptate tergeo pariatur conor argumentum inventore vomito stella.',\n", + " metadata: {\n", + " id: 'f1a04800-6f7a-4232-9743-fb5d9029bf1f',\n", + " created_at: '2024-08-20T17:01:38.984045+00:00',\n", + " modified_at: '2024-08-20T17:01:38.984045+00:00',\n", + " name: '#f1a0 @ LangSmith Few Shot Datasets Notebook',\n", + " dataset_id: '9ccd66e6-e506-478c-9095-3d9e27575a89',\n", + " source_run_id: null,\n", + " metadata: {\n", + " dataset_split: [Array],\n", + " companyCatchPhrase: 'Integrated solution-oriented secured line'\n", + " },\n", + " inputs: {\n", + " input: 'Conventus supellex aegrotatio termes. Vapulus abscido ubi vita coadunatio modi crapula comparo caecus. Acervus voluptate tergeo pariatur conor argumentum inventore vomito stella.'\n", + " },\n", + " outputs: {\n", + " output: 'Excepturi adeptio spectaculum bis volaticus accusamus.'\n", + " }\n", + " }\n", + "}\n" + ] + } + ], + "source": [ + "const docs = await loader.load()\n", + "docs[0]" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "{\n", + " id: 'f1a04800-6f7a-4232-9743-fb5d9029bf1f',\n", + " created_at: '2024-08-20T17:01:38.984045+00:00',\n", + " modified_at: '2024-08-20T17:01:38.984045+00:00',\n", + " name: '#f1a0 @ LangSmith Few Shot Datasets Notebook',\n", + " dataset_id: '9ccd66e6-e506-478c-9095-3d9e27575a89',\n", + " source_run_id: null,\n", + " metadata: {\n", + " dataset_split: [ 'base' ],\n", + " companyCatchPhrase: 'Integrated solution-oriented secured line'\n", + " },\n", + " inputs: {\n", + " input: 'Conventus supellex aegrotatio termes. Vapulus abscido ubi vita coadunatio modi crapula comparo caecus. Acervus voluptate tergeo pariatur conor argumentum inventore vomito stella.'\n", + " },\n", + " outputs: { output: 'Excepturi adeptio spectaculum bis volaticus accusamus.' }\n", + "}\n" + ] + } + ], + "source": [ + "console.log(docs[0].metadata)" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "{\n", + " input: 'Conventus supellex aegrotatio termes. Vapulus abscido ubi vita coadunatio modi crapula comparo caecus. Acervus voluptate tergeo pariatur conor argumentum inventore vomito stella.'\n", + "}\n" + ] + } + ], + "source": [ + "console.log(docs[0].metadata.inputs)" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "{ output: 'Excepturi adeptio spectaculum bis volaticus accusamus.' }\n" + ] + } + ], + "source": [ + "console.log(docs[0].metadata.outputs)" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[\n", + " 'id',\n", + " 'created_at',\n", + " 'modified_at',\n", + " 'name',\n", + " 'dataset_id',\n", + " 'source_run_id',\n", + " 'metadata',\n", + " 'inputs',\n", + " 'outputs'\n", + "]\n" + ] + } + ], + "source": [ + "console.log(Object.keys(docs[0].metadata))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## API reference\n", + "\n", + "For detailed documentation of all `LangSmithLoader` features and configurations head to the [API reference](https://api.js.langchain.com/classes/_langchain_core.document_loaders_langsmith.LangSmithLoader.html)" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "TypeScript", + "language": "typescript", + "name": "tslab" + }, + "language_info": { + "codemirror_mode": { + "mode": "typescript", + "name": "javascript", + "typescript": true + }, + "file_extension": ".ts", + "mimetype": "text/typescript", + "name": "typescript", + "version": "3.7.2" + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/langchain-core/.gitignore b/langchain-core/.gitignore index 4791f5d14dab..efc6a4dc88ba 100644 --- a/langchain-core/.gitignore +++ b/langchain-core/.gitignore @@ -38,6 +38,10 @@ document_loaders/base.cjs document_loaders/base.js document_loaders/base.d.ts document_loaders/base.d.cts +document_loaders/langsmith.cjs +document_loaders/langsmith.js +document_loaders/langsmith.d.ts +document_loaders/langsmith.d.cts embeddings.cjs embeddings.js embeddings.d.ts diff --git a/langchain-core/langchain.config.js b/langchain-core/langchain.config.js index 620f4d3139db..b7fd982357ee 100644 --- a/langchain-core/langchain.config.js +++ b/langchain-core/langchain.config.js @@ -22,6 +22,7 @@ export const config = { chat_history: "chat_history", documents: "documents/index", "document_loaders/base": "document_loaders/base", + "document_loaders/langsmith": "document_loaders/langsmith", embeddings: "embeddings", example_selectors: "example_selectors/index", indexing: "indexing/index", diff --git a/langchain-core/package.json b/langchain-core/package.json index ed901e978c36..2768e05daa01 100644 --- a/langchain-core/package.json +++ b/langchain-core/package.json @@ -186,6 +186,15 @@ "import": "./document_loaders/base.js", "require": "./document_loaders/base.cjs" }, + "./document_loaders/langsmith": { + "types": { + "import": "./document_loaders/langsmith.d.ts", + "require": "./document_loaders/langsmith.d.cts", + "default": "./document_loaders/langsmith.d.ts" + }, + "import": "./document_loaders/langsmith.js", + "require": "./document_loaders/langsmith.cjs" + }, "./embeddings": { "types": { "import": "./embeddings.d.ts", @@ -653,6 +662,10 @@ "document_loaders/base.js", "document_loaders/base.d.ts", "document_loaders/base.d.cts", + "document_loaders/langsmith.cjs", + "document_loaders/langsmith.js", + "document_loaders/langsmith.d.ts", + "document_loaders/langsmith.d.cts", "embeddings.cjs", "embeddings.js", "embeddings.d.ts", diff --git a/langchain-core/src/document_loaders/langsmith.ts b/langchain-core/src/document_loaders/langsmith.ts new file mode 100644 index 000000000000..d41d0a2740f3 --- /dev/null +++ b/langchain-core/src/document_loaders/langsmith.ts @@ -0,0 +1,182 @@ +import { KVMap } from "langsmith/schemas"; +import { Client } from "langsmith"; +import { Document, DocumentInterface } from "../documents/document.js"; +import { AsyncCallerParams } from "../utils/async_caller.js"; +import { BaseDocumentLoader } from "./base.js"; + +// TODO: Replace with import from `langsmith` once exposed. +interface ClientConfig { + apiUrl?: string; + apiKey?: string; + callerOptions?: AsyncCallerParams; + timeout_ms?: number; + webUrl?: string; + anonymizer?: (values: KVMap) => KVMap; + hideInputs?: boolean | ((inputs: KVMap) => KVMap); + hideOutputs?: boolean | ((outputs: KVMap) => KVMap); + autoBatchTracing?: boolean; + pendingAutoBatchedRunLimit?: number; + fetchOptions?: RequestInit; +} + +export interface LangSmithLoaderFields { + datasetId?: string; + datasetName?: string; + exampleIds?: Array; + asOf?: Date | string; + splits?: string[]; + inlineS3Urls?: boolean; + offset?: number; + limit?: number; + metadata?: KVMap; + filter?: string; + contentKey?: string; + // eslint-disable-next-line @typescript-eslint/no-explicit-any + formatContent?: (content: any) => string; + client?: Client; + clientConfig?: ClientConfig; +} + +/** + * Document loader integration with LangSmith. + * + * ## [Constructor args](https://api.js.langchain.com/interfaces/_langchain_core.document_loaders_langsmith.LangSmithLoaderFields.html) + * + *
+ * Load + * + * ```typescript + * import { LangSmithLoader } from '@langchain/core/document_loaders/langsmith'; + * import { Client } from 'langsmith'; + * + * const langSmithClient = new Client({ + * apiKey: process.env.LANGSMITH_API_KEY, + * }) + * + * const loader = new LangSmithLoader({ + * datasetId: "9a3b36f7-b308-40a5-9b46-6613853b6330", + * limit: 1, + * }); + * + * const docs = await loader.load(); + * ``` + * + * ```txt + * [ + * { + * pageContent: '{\n "input_key_str": "string",\n "input_key_bool": true\n}', + * metadata: { + * id: '8523d9e9-c123-4b23-9b46-21021nds289e', + * created_at: '2024-08-19T17:09:14.806441+00:00', + * modified_at: '2024-08-19T17:09:14.806441+00:00', + * name: '#8517 @ brace-test-dataset', + * dataset_id: '9a3b36f7-b308-40a5-9b46-6613853b6330', + * source_run_id: null, + * metadata: [Object], + * inputs: [Object], + * outputs: [Object] + * } + * } + * ] + * ``` + *
+ */ +export class LangSmithLoader extends BaseDocumentLoader { + datasetId?: string; + + datasetName?: string; + + exampleIds?: Array; + + asOf?: Date | string; + + splits?: string[]; + + inlineS3Urls?: boolean; + + offset?: number; + + limit?: number; + + metadata?: KVMap; + + filter?: string; + + contentKey: string[]; + + // eslint-disable-next-line @typescript-eslint/no-explicit-any + formatContent: (content: any) => string; + + client: Client; + + constructor(fields: LangSmithLoaderFields) { + super(); + + if (fields.client && fields.clientConfig) { + throw new Error("client and clientConfig cannot both be provided."); + } + this.client = fields.client ?? new Client(fields?.clientConfig); + this.contentKey = fields.contentKey ? fields.contentKey.split(".") : []; + this.formatContent = fields.formatContent ?? _stringify; + this.datasetId = fields.datasetId; + this.datasetName = fields.datasetName; + this.exampleIds = fields.exampleIds; + this.asOf = fields.asOf; + this.splits = fields.splits; + this.inlineS3Urls = fields.inlineS3Urls; + this.offset = fields.offset; + this.limit = fields.limit; + this.metadata = fields.metadata; + this.filter = fields.filter; + } + + async load(): Promise { + const documents: DocumentInterface[] = []; + for await (const example of this.client.listExamples({ + datasetId: this.datasetId, + datasetName: this.datasetName, + exampleIds: this.exampleIds, + asOf: this.asOf, + splits: this.splits, + inlineS3Urls: this.inlineS3Urls, + offset: this.offset, + limit: this.limit, + metadata: this.metadata, + filter: this.filter, + })) { + let content = example.inputs; + for (const key of this.contentKey) { + content = content[key]; + } + const contentStr = this.formatContent(content); + + const metadata: KVMap = example; + ["created_at", "modified_at"].forEach((k) => { + if (k in metadata) { + if (typeof metadata[k] === "object") { + // Dates are of type `object`, we want to convert them to strings. + metadata[k] = metadata[k].toString(); + } + } + }); + + documents.push({ + pageContent: contentStr, + metadata, + }); + } + return documents; + } +} + +function _stringify(x: string | KVMap): string { + if (typeof x === "string") { + return x; + } else { + try { + return JSON.stringify(x, null, 2); + } catch (error) { + return String(x); + } + } +} diff --git a/langchain-core/src/document_loaders/tests/langsmith.int.test.ts b/langchain-core/src/document_loaders/tests/langsmith.int.test.ts new file mode 100644 index 000000000000..a546bbf56847 --- /dev/null +++ b/langchain-core/src/document_loaders/tests/langsmith.int.test.ts @@ -0,0 +1,67 @@ +/* eslint-disable no-process-env */ +import { test, expect } from "@jest/globals"; +import { Client } from "langsmith"; +import { LangSmithLoader } from "../langsmith.js"; + +const DATASET_NAME = "brace-test-dataset"; +const DATASET_ID = "9a3b36f7-a297-40a5-944d-6613853b6330"; + +test("LangSmithLoader can load with client passed in", async () => { + const lsClient = new Client(); + const loader = new LangSmithLoader({ + datasetId: DATASET_ID, + client: lsClient, + }); + const docs = await loader.load(); + + expect(docs.length).toBeGreaterThanOrEqual(1); + console.log(docs[0]); + const parsedContent = JSON.parse(docs[0].pageContent); + expect(parsedContent).toHaveProperty("input_key_str"); + expect(parsedContent.input_key_str).toBe("string"); + expect(parsedContent).toHaveProperty("input_key_bool"); + expect(parsedContent.input_key_bool).toBe(true); + + expect(docs[0].metadata).toHaveProperty("created_at"); + expect(typeof docs[0].metadata.created_at).toBe("string"); + expect(docs[0].metadata).toHaveProperty("modified_at"); + expect(typeof docs[0].metadata.modified_at).toBe("string"); +}); + +test("LangSmithLoader can load with client options passed in", async () => { + const lsApiKey = process.env.LANGCHAIN_API_KEY; + // unassign the API key to confirm the client isn't overriding what we passed in. + process.env.LANGCHAIN_API_KEY = ""; + + try { + const lsConfigArgs = { + apiKey: lsApiKey, + }; + const loader = new LangSmithLoader({ + datasetId: DATASET_ID, + clientConfig: lsConfigArgs, + }); + const docs = await loader.load(); + + expect(docs.length).toBeGreaterThanOrEqual(1); + } finally { + process.env.LANGCHAIN_API_KEY = lsApiKey; + } +}); + +test("LangSmithLoader can load with dataset name", async () => { + const loader = new LangSmithLoader({ datasetName: DATASET_NAME }); + const docs = await loader.load(); + + expect(docs.length).toBeGreaterThanOrEqual(1); +}); + +test("Passing content key correctly loads that value", async () => { + const loader = new LangSmithLoader({ + datasetName: DATASET_NAME, + contentKey: "input_key_str", + }); + const docs = await loader.load(); + + expect(docs[0].pageContent).toBe("string"); +}); From b013316607975e25eba65c4c28388fa5060ffbed Mon Sep 17 00:00:00 2001 From: Brace Sproul Date: Wed, 21 Aug 2024 11:37:05 -0700 Subject: [PATCH 066/344] core[patch]: Release 0.2.28 (#6587) --- 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 2768e05daa01..79028720f777 100644 --- a/langchain-core/package.json +++ b/langchain-core/package.json @@ -1,6 +1,6 @@ { "name": "@langchain/core", - "version": "0.2.27", + "version": "0.2.28", "description": "Core LangChain.js abstractions and schemas", "type": "module", "engines": { From b0ae2b19e98af6b319d89d0dd9049afd757e9899 Mon Sep 17 00:00:00 2001 From: Brace Sproul Date: Wed, 21 Aug 2024 11:55:29 -0700 Subject: [PATCH 067/344] partners[patch]: Bump langchain scripts version (#6586) * partners[patch]: Bump langchain scripts version * drop --- examples/package.json | 2 +- langchain-core/package.json | 2 +- langchain/package.json | 2 +- .../template/package.json | 4 +- libs/langchain-anthropic/package.json | 2 +- libs/langchain-aws/package.json | 2 +- .../package.json | 2 +- libs/langchain-azure-openai/package.json | 2 +- libs/langchain-baidu-qianfan/package.json | 2 +- libs/langchain-cloudflare/package.json | 2 +- libs/langchain-cohere/package.json | 2 +- libs/langchain-community/package.json | 2 +- libs/langchain-exa/package.json | 2 +- libs/langchain-google-common/package.json | 2 +- libs/langchain-google-gauth/package.json | 2 +- libs/langchain-google-genai/package.json | 2 +- .../package.json | 2 +- libs/langchain-google-vertexai/package.json | 2 +- libs/langchain-google-webauth/package.json | 2 +- libs/langchain-groq/package.json | 2 +- libs/langchain-mistralai/package.json | 2 +- libs/langchain-mixedbread-ai/package.json | 2 +- libs/langchain-mongodb/package.json | 2 +- libs/langchain-nomic/package.json | 2 +- libs/langchain-ollama/package.json | 2 +- libs/langchain-openai/package.json | 2 +- libs/langchain-pinecone/package.json | 2 +- libs/langchain-qdrant/package.json | 2 +- libs/langchain-redis/package.json | 2 +- libs/langchain-textsplitters/package.json | 2 +- libs/langchain-weaviate/package.json | 2 +- libs/langchain-yandex/package.json | 2 +- yarn.lock | 86 +++++++------------ 33 files changed, 65 insertions(+), 87 deletions(-) diff --git a/examples/package.json b/examples/package.json index 579c3be83a72..a109009b46a2 100644 --- a/examples/package.json +++ b/examples/package.json @@ -58,7 +58,7 @@ "@langchain/pinecone": "workspace:*", "@langchain/qdrant": "workspace:*", "@langchain/redis": "workspace:*", - "@langchain/scripts": "^0.0.21", + "@langchain/scripts": "^0.0.22", "@langchain/textsplitters": "workspace:*", "@langchain/weaviate": "workspace:*", "@langchain/yandex": "workspace:*", diff --git a/langchain-core/package.json b/langchain-core/package.json index 79028720f777..abe896a07b17 100644 --- a/langchain-core/package.json +++ b/langchain-core/package.json @@ -55,7 +55,7 @@ }, "devDependencies": { "@jest/globals": "^29.5.0", - "@langchain/scripts": "^0.0.21", + "@langchain/scripts": "^0.0.22", "@swc/core": "^1.3.90", "@swc/jest": "^0.2.29", "@types/decamelize": "^1.2.0", diff --git a/langchain/package.json b/langchain/package.json index 8c5d13a9b751..6263efc4bf71 100644 --- a/langchain/package.json +++ b/langchain/package.json @@ -621,7 +621,7 @@ "@langchain/groq": "^0.0.15", "@langchain/mistralai": "^0.0.26", "@langchain/ollama": "^0.0.2", - "@langchain/scripts": "^0.0.21", + "@langchain/scripts": "^0.0.22", "@mendable/firecrawl-js": "^0.0.13", "@notionhq/client": "^2.2.10", "@pinecone-database/pinecone": "^1.1.0", diff --git a/libs/create-langchain-integration/template/package.json b/libs/create-langchain-integration/template/package.json index 326580217a7b..5b5b48eafe24 100644 --- a/libs/create-langchain-integration/template/package.json +++ b/libs/create-langchain-integration/template/package.json @@ -45,7 +45,7 @@ "@jest/globals": "^29.5.0", "@swc/core": "^1.3.90", "@swc/jest": "^0.2.29", - "@langchain/scripts": "^0.0.21", + "@langchain/scripts": "^0.0.22", "@tsconfig/recommended": "^1.0.3", "@typescript-eslint/eslint-plugin": "^6.12.0", "@typescript-eslint/parser": "^6.12.0", @@ -82,4 +82,4 @@ "index.js", "index.d.ts" ] -} +} \ No newline at end of file diff --git a/libs/langchain-anthropic/package.json b/libs/langchain-anthropic/package.json index ea843eab9532..5bc74d744bba 100644 --- a/libs/langchain-anthropic/package.json +++ b/libs/langchain-anthropic/package.json @@ -43,7 +43,7 @@ }, "devDependencies": { "@jest/globals": "^29.5.0", - "@langchain/scripts": "^0.0.21", + "@langchain/scripts": "^0.0.22", "@langchain/standard-tests": "0.0.0", "@swc/core": "^1.3.90", "@swc/jest": "^0.2.29", diff --git a/libs/langchain-aws/package.json b/libs/langchain-aws/package.json index 48e4d63696bb..1d4be50baed5 100644 --- a/libs/langchain-aws/package.json +++ b/libs/langchain-aws/package.json @@ -50,7 +50,7 @@ "devDependencies": { "@aws-sdk/types": "^3.609.0", "@jest/globals": "^29.5.0", - "@langchain/scripts": "^0.0.21", + "@langchain/scripts": "^0.0.22", "@langchain/standard-tests": "0.0.0", "@smithy/types": "^3.2.0", "@swc/core": "^1.3.90", diff --git a/libs/langchain-azure-dynamic-sessions/package.json b/libs/langchain-azure-dynamic-sessions/package.json index b6504cc0be2a..c503fcd5f74c 100644 --- a/libs/langchain-azure-dynamic-sessions/package.json +++ b/libs/langchain-azure-dynamic-sessions/package.json @@ -44,7 +44,7 @@ }, "devDependencies": { "@jest/globals": "^29.5.0", - "@langchain/scripts": "^0.0.21", + "@langchain/scripts": "^0.0.22", "@swc/core": "^1.3.90", "@swc/jest": "^0.2.29", "@tsconfig/recommended": "^1.0.3", diff --git a/libs/langchain-azure-openai/package.json b/libs/langchain-azure-openai/package.json index a386398a2fc9..0899a8d8b698 100644 --- a/libs/langchain-azure-openai/package.json +++ b/libs/langchain-azure-openai/package.json @@ -41,7 +41,7 @@ "devDependencies": { "@azure/identity": "^4.2.1", "@jest/globals": "^29.5.0", - "@langchain/scripts": "^0.0.21", + "@langchain/scripts": "^0.0.22", "@langchain/standard-tests": "0.0.0", "@swc/core": "^1.3.90", "@swc/jest": "^0.2.29", diff --git a/libs/langchain-baidu-qianfan/package.json b/libs/langchain-baidu-qianfan/package.json index ae90a64fdf2e..3fa2d51c7627 100644 --- a/libs/langchain-baidu-qianfan/package.json +++ b/libs/langchain-baidu-qianfan/package.json @@ -40,7 +40,7 @@ "devDependencies": { "@jest/globals": "^29.5.0", "@langchain/openai": "~0.1.0", - "@langchain/scripts": "^0.0.21", + "@langchain/scripts": "^0.0.22", "@swc/core": "^1.3.90", "@swc/jest": "^0.2.29", "@tsconfig/recommended": "^1.0.3", diff --git a/libs/langchain-cloudflare/package.json b/libs/langchain-cloudflare/package.json index af14fba26d76..7633679dde7d 100644 --- a/libs/langchain-cloudflare/package.json +++ b/libs/langchain-cloudflare/package.json @@ -43,7 +43,7 @@ "@cloudflare/workers-types": "^4.20231218.0", "@jest/globals": "^29.5.0", "@langchain/langgraph": "~0.0.31", - "@langchain/scripts": "^0.0.21", + "@langchain/scripts": "^0.0.22", "@langchain/standard-tests": "0.0.0", "@swc/core": "^1.3.90", "@swc/jest": "^0.2.29", diff --git a/libs/langchain-cohere/package.json b/libs/langchain-cohere/package.json index 27ed82d1eadb..436b0c776b87 100644 --- a/libs/langchain-cohere/package.json +++ b/libs/langchain-cohere/package.json @@ -43,7 +43,7 @@ }, "devDependencies": { "@jest/globals": "^29.5.0", - "@langchain/scripts": "^0.0.21", + "@langchain/scripts": "^0.0.22", "@langchain/standard-tests": "0.0.0", "@swc/core": "^1.3.90", "@swc/jest": "^0.2.29", diff --git a/libs/langchain-community/package.json b/libs/langchain-community/package.json index 83bd6cecae25..63e8945eea79 100644 --- a/libs/langchain-community/package.json +++ b/libs/langchain-community/package.json @@ -79,7 +79,7 @@ "@huggingface/inference": "^2.6.4", "@jest/globals": "^29.5.0", "@langchain/langgraph": "<0.1.0", - "@langchain/scripts": "^0.0.21", + "@langchain/scripts": "^0.0.22", "@langchain/standard-tests": "0.0.0", "@layerup/layerup-security": "^1.5.12", "@mendable/firecrawl-js": "^0.0.36", diff --git a/libs/langchain-exa/package.json b/libs/langchain-exa/package.json index b4e74ae0387c..7144cbab2ae9 100644 --- a/libs/langchain-exa/package.json +++ b/libs/langchain-exa/package.json @@ -45,7 +45,7 @@ }, "devDependencies": { "@jest/globals": "^29.5.0", - "@langchain/scripts": "^0.0.21", + "@langchain/scripts": "^0.0.22", "@swc/core": "^1.3.90", "@swc/jest": "^0.2.29", "@tsconfig/recommended": "^1.0.3", diff --git a/libs/langchain-google-common/package.json b/libs/langchain-google-common/package.json index 0aba010d59fd..6fad85806da3 100644 --- a/libs/langchain-google-common/package.json +++ b/libs/langchain-google-common/package.json @@ -46,7 +46,7 @@ }, "devDependencies": { "@jest/globals": "^29.5.0", - "@langchain/scripts": "^0.0.21", + "@langchain/scripts": "^0.0.22", "@swc/core": "^1.3.90", "@swc/jest": "^0.2.29", "@tsconfig/recommended": "^1.0.3", diff --git a/libs/langchain-google-gauth/package.json b/libs/langchain-google-gauth/package.json index 3a9190076726..b4e6b33e2ec2 100644 --- a/libs/langchain-google-gauth/package.json +++ b/libs/langchain-google-gauth/package.json @@ -41,7 +41,7 @@ }, "devDependencies": { "@jest/globals": "^29.5.0", - "@langchain/scripts": "^0.0.21", + "@langchain/scripts": "^0.0.22", "@swc/core": "^1.3.90", "@swc/jest": "^0.2.29", "@tsconfig/recommended": "^1.0.3", diff --git a/libs/langchain-google-genai/package.json b/libs/langchain-google-genai/package.json index fdf63cc93a04..ac6bbda0e9c2 100644 --- a/libs/langchain-google-genai/package.json +++ b/libs/langchain-google-genai/package.json @@ -41,7 +41,7 @@ }, "devDependencies": { "@jest/globals": "^29.5.0", - "@langchain/scripts": "^0.0.21", + "@langchain/scripts": "^0.0.22", "@langchain/standard-tests": "0.0.0", "@swc/core": "^1.3.90", "@swc/jest": "^0.2.29", diff --git a/libs/langchain-google-vertexai-web/package.json b/libs/langchain-google-vertexai-web/package.json index 916b95ff069a..f63b59e797fd 100644 --- a/libs/langchain-google-vertexai-web/package.json +++ b/libs/langchain-google-vertexai-web/package.json @@ -46,7 +46,7 @@ "devDependencies": { "@jest/globals": "^29.5.0", "@langchain/google-common": "~0.0", - "@langchain/scripts": "^0.0.21", + "@langchain/scripts": "^0.0.22", "@langchain/standard-tests": "0.0.0", "@swc/core": "^1.3.90", "@swc/jest": "^0.2.29", diff --git a/libs/langchain-google-vertexai/package.json b/libs/langchain-google-vertexai/package.json index e8094e28f5cf..92f98bb8db0c 100644 --- a/libs/langchain-google-vertexai/package.json +++ b/libs/langchain-google-vertexai/package.json @@ -49,7 +49,7 @@ "devDependencies": { "@jest/globals": "^29.5.0", "@langchain/google-common": "~0.0", - "@langchain/scripts": "^0.0.21", + "@langchain/scripts": "^0.0.22", "@langchain/standard-tests": "0.0.0", "@swc/core": "^1.3.90", "@swc/jest": "^0.2.29", diff --git a/libs/langchain-google-webauth/package.json b/libs/langchain-google-webauth/package.json index 055e54d8e893..4e2d1072a0ad 100644 --- a/libs/langchain-google-webauth/package.json +++ b/libs/langchain-google-webauth/package.json @@ -46,7 +46,7 @@ }, "devDependencies": { "@jest/globals": "^29.5.0", - "@langchain/scripts": "^0.0.21", + "@langchain/scripts": "^0.0.22", "@swc/core": "^1.3.90", "@swc/jest": "^0.2.29", "@tsconfig/recommended": "^1.0.3", diff --git a/libs/langchain-groq/package.json b/libs/langchain-groq/package.json index d5c1f09c0c86..f2648b4f3a9e 100644 --- a/libs/langchain-groq/package.json +++ b/libs/langchain-groq/package.json @@ -44,7 +44,7 @@ "devDependencies": { "@jest/globals": "^29.5.0", "@langchain/openai": "workspace:^", - "@langchain/scripts": "^0.0.21", + "@langchain/scripts": "^0.0.22", "@langchain/standard-tests": "0.0.0", "@swc/core": "^1.3.90", "@swc/jest": "^0.2.29", diff --git a/libs/langchain-mistralai/package.json b/libs/langchain-mistralai/package.json index 18518b9ed0a5..121e9f21092c 100644 --- a/libs/langchain-mistralai/package.json +++ b/libs/langchain-mistralai/package.json @@ -43,7 +43,7 @@ }, "devDependencies": { "@jest/globals": "^29.5.0", - "@langchain/scripts": "^0.0.21", + "@langchain/scripts": "^0.0.22", "@langchain/standard-tests": "0.0.0", "@swc/core": "^1.3.90", "@swc/jest": "^0.2.29", diff --git a/libs/langchain-mixedbread-ai/package.json b/libs/langchain-mixedbread-ai/package.json index bdd5c3319687..5317646d976c 100644 --- a/libs/langchain-mixedbread-ai/package.json +++ b/libs/langchain-mixedbread-ai/package.json @@ -40,7 +40,7 @@ }, "devDependencies": { "@jest/globals": "^29.5.0", - "@langchain/scripts": "^0.0.21", + "@langchain/scripts": "^0.0.22", "@langchain/standard-tests": "0.0.0", "@swc/core": "^1.3.90", "@swc/jest": "^0.2.29", diff --git a/libs/langchain-mongodb/package.json b/libs/langchain-mongodb/package.json index ea068045df7f..91c26fb525d1 100644 --- a/libs/langchain-mongodb/package.json +++ b/libs/langchain-mongodb/package.json @@ -46,7 +46,7 @@ "devDependencies": { "@jest/globals": "^29.5.0", "@langchain/openai": "workspace:*", - "@langchain/scripts": "^0.0.21", + "@langchain/scripts": "^0.0.22", "@swc/core": "^1.3.90", "@swc/jest": "^0.2.29", "@tsconfig/recommended": "^1.0.3", diff --git a/libs/langchain-nomic/package.json b/libs/langchain-nomic/package.json index 8b347543fb53..2168b199c976 100644 --- a/libs/langchain-nomic/package.json +++ b/libs/langchain-nomic/package.json @@ -46,7 +46,7 @@ "devDependencies": { "@jest/globals": "^29.5.0", "@langchain/openai": "workspace:^", - "@langchain/scripts": "^0.0.21", + "@langchain/scripts": "^0.0.22", "@swc/core": "^1.3.90", "@swc/jest": "^0.2.29", "@tsconfig/recommended": "^1.0.3", diff --git a/libs/langchain-ollama/package.json b/libs/langchain-ollama/package.json index e2b041f03c77..c008be49ca9c 100644 --- a/libs/langchain-ollama/package.json +++ b/libs/langchain-ollama/package.json @@ -41,7 +41,7 @@ }, "devDependencies": { "@jest/globals": "^29.5.0", - "@langchain/scripts": "^0.0.21", + "@langchain/scripts": "^0.0.22", "@langchain/standard-tests": "0.0.0", "@swc/core": "^1.3.90", "@swc/jest": "^0.2.29", diff --git a/libs/langchain-openai/package.json b/libs/langchain-openai/package.json index 1e372dcf3305..738b6a76e84e 100644 --- a/libs/langchain-openai/package.json +++ b/libs/langchain-openai/package.json @@ -44,7 +44,7 @@ "devDependencies": { "@azure/identity": "^4.2.1", "@jest/globals": "^29.5.0", - "@langchain/scripts": "^0.0.21", + "@langchain/scripts": "^0.0.22", "@langchain/standard-tests": "0.0.0", "@swc/core": "^1.3.90", "@swc/jest": "^0.2.29", diff --git a/libs/langchain-pinecone/package.json b/libs/langchain-pinecone/package.json index ff1e1eef2b45..12cc7eaec7ba 100644 --- a/libs/langchain-pinecone/package.json +++ b/libs/langchain-pinecone/package.json @@ -48,7 +48,7 @@ "@faker-js/faker": "^8.3.1", "@jest/globals": "^29.5.0", "@langchain/openai": "workspace:*", - "@langchain/scripts": "^0.0.21", + "@langchain/scripts": "^0.0.22", "@swc/core": "^1.3.90", "@swc/jest": "^0.2.29", "@tsconfig/recommended": "^1.0.3", diff --git a/libs/langchain-qdrant/package.json b/libs/langchain-qdrant/package.json index b3a4c254fb9f..a8bd1aefa31e 100644 --- a/libs/langchain-qdrant/package.json +++ b/libs/langchain-qdrant/package.json @@ -46,7 +46,7 @@ "devDependencies": { "@faker-js/faker": "^8.4.1", "@jest/globals": "^29.5.0", - "@langchain/scripts": "^0.0.21", + "@langchain/scripts": "^0.0.22", "@swc/core": "^1.3.90", "@swc/jest": "^0.2.29", "@tsconfig/recommended": "^1.0.3", diff --git a/libs/langchain-redis/package.json b/libs/langchain-redis/package.json index 4d3f13c76531..1903dead9b03 100644 --- a/libs/langchain-redis/package.json +++ b/libs/langchain-redis/package.json @@ -45,7 +45,7 @@ "devDependencies": { "@faker-js/faker": "^8.4.0", "@jest/globals": "^29.5.0", - "@langchain/scripts": "^0.0.21", + "@langchain/scripts": "^0.0.22", "@swc/core": "^1.3.90", "@swc/jest": "^0.2.29", "@tsconfig/recommended": "^1.0.3", diff --git a/libs/langchain-textsplitters/package.json b/libs/langchain-textsplitters/package.json index 1efb1da6a906..a610b2ac5823 100644 --- a/libs/langchain-textsplitters/package.json +++ b/libs/langchain-textsplitters/package.json @@ -44,7 +44,7 @@ }, "devDependencies": { "@jest/globals": "^29.5.0", - "@langchain/scripts": "^0.0.21", + "@langchain/scripts": "^0.0.22", "@swc/core": "^1.3.90", "@swc/jest": "^0.2.29", "@tsconfig/recommended": "^1.0.3", diff --git a/libs/langchain-weaviate/package.json b/libs/langchain-weaviate/package.json index d7939017b918..7ad79c6a7984 100644 --- a/libs/langchain-weaviate/package.json +++ b/libs/langchain-weaviate/package.json @@ -47,7 +47,7 @@ "devDependencies": { "@jest/globals": "^29.5.0", "@langchain/openai": "workspace:^", - "@langchain/scripts": "^0.0.21", + "@langchain/scripts": "^0.0.22", "@swc/core": "^1.3.90", "@swc/jest": "^0.2.29", "@tsconfig/recommended": "^1.0.3", diff --git a/libs/langchain-yandex/package.json b/libs/langchain-yandex/package.json index 6d4e57ced8c1..b194e416265d 100644 --- a/libs/langchain-yandex/package.json +++ b/libs/langchain-yandex/package.json @@ -44,7 +44,7 @@ }, "devDependencies": { "@jest/globals": "^29.5.0", - "@langchain/scripts": "^0.0.21", + "@langchain/scripts": "^0.0.22", "@swc/core": "^1.3.90", "@swc/jest": "^0.2.29", "@tsconfig/recommended": "^1.0.3", diff --git a/yarn.lock b/yarn.lock index dd138da859d4..9b54943d05c5 100644 --- a/yarn.lock +++ b/yarn.lock @@ -10854,7 +10854,7 @@ __metadata: "@anthropic-ai/sdk": ^0.25.2 "@jest/globals": ^29.5.0 "@langchain/core": ">=0.2.21 <0.3.0" - "@langchain/scripts": ^0.0.21 + "@langchain/scripts": ^0.0.22 "@langchain/standard-tests": 0.0.0 "@swc/core": ^1.3.90 "@swc/jest": ^0.2.29 @@ -10904,7 +10904,7 @@ __metadata: "@aws-sdk/types": ^3.609.0 "@jest/globals": ^29.5.0 "@langchain/core": ">=0.2.21 <0.3.0" - "@langchain/scripts": ^0.0.21 + "@langchain/scripts": ^0.0.22 "@langchain/standard-tests": 0.0.0 "@smithy/types": ^3.2.0 "@swc/core": ^1.3.90 @@ -10973,7 +10973,7 @@ __metadata: "@azure/identity": ^4.2.1 "@jest/globals": ^29.5.0 "@langchain/core": ~0.2 - "@langchain/scripts": ^0.0.21 + "@langchain/scripts": ^0.0.22 "@swc/core": ^1.3.90 "@swc/jest": ^0.2.29 "@tsconfig/recommended": ^1.0.3 @@ -11008,7 +11008,7 @@ __metadata: "@azure/openai": 1.0.0-beta.11 "@jest/globals": ^29.5.0 "@langchain/core": ">0.1.0 <0.3.0" - "@langchain/scripts": ^0.0.21 + "@langchain/scripts": ^0.0.22 "@langchain/standard-tests": 0.0.0 "@swc/core": ^1.3.90 "@swc/jest": ^0.2.29 @@ -11040,7 +11040,7 @@ __metadata: "@jest/globals": ^29.5.0 "@langchain/core": ">0.1.56 <0.3.0" "@langchain/openai": ~0.1.0 - "@langchain/scripts": ^0.0.21 + "@langchain/scripts": ^0.0.22 "@swc/core": ^1.3.90 "@swc/jest": ^0.2.29 "@tsconfig/recommended": ^1.0.3 @@ -11076,7 +11076,7 @@ __metadata: "@jest/globals": ^29.5.0 "@langchain/core": ">0.1.0 <0.3.0" "@langchain/langgraph": ~0.0.31 - "@langchain/scripts": ^0.0.21 + "@langchain/scripts": ^0.0.22 "@langchain/standard-tests": 0.0.0 "@swc/core": ^1.3.90 "@swc/jest": ^0.2.29 @@ -11114,7 +11114,7 @@ __metadata: dependencies: "@jest/globals": ^29.5.0 "@langchain/core": ">=0.2.21 <0.3.0" - "@langchain/scripts": ^0.0.21 + "@langchain/scripts": ^0.0.22 "@langchain/standard-tests": 0.0.0 "@swc/core": ^1.3.90 "@swc/jest": ^0.2.29 @@ -11181,7 +11181,7 @@ __metadata: "@langchain/core": ">=0.2.21 <0.3.0" "@langchain/langgraph": <0.1.0 "@langchain/openai": ">=0.2.0 <0.3.0" - "@langchain/scripts": ^0.0.21 + "@langchain/scripts": ^0.0.22 "@langchain/standard-tests": 0.0.0 "@layerup/layerup-security": ^1.5.12 "@mendable/firecrawl-js": ^0.0.36 @@ -11692,7 +11692,7 @@ __metadata: resolution: "@langchain/core@workspace:langchain-core" dependencies: "@jest/globals": ^29.5.0 - "@langchain/scripts": ^0.0.21 + "@langchain/scripts": ^0.0.22 "@swc/core": ^1.3.90 "@swc/jest": ^0.2.29 "@types/decamelize": ^1.2.0 @@ -11734,7 +11734,7 @@ __metadata: dependencies: "@jest/globals": ^29.5.0 "@langchain/core": ">0.1.0 <0.3.0" - "@langchain/scripts": ^0.0.21 + "@langchain/scripts": ^0.0.22 "@swc/core": ^1.3.90 "@swc/jest": ^0.2.29 "@tsconfig/recommended": ^1.0.3 @@ -11766,7 +11766,7 @@ __metadata: dependencies: "@jest/globals": ^29.5.0 "@langchain/core": ">=0.2.21 <0.3.0" - "@langchain/scripts": ^0.0.21 + "@langchain/scripts": ^0.0.22 "@swc/core": ^1.3.90 "@swc/jest": ^0.2.29 "@tsconfig/recommended": ^1.0.3 @@ -11800,7 +11800,7 @@ __metadata: "@jest/globals": ^29.5.0 "@langchain/core": ">=0.2.21 <0.3.0" "@langchain/google-common": ~0.0.26 - "@langchain/scripts": ^0.0.21 + "@langchain/scripts": ^0.0.22 "@swc/core": ^1.3.90 "@swc/jest": ^0.2.29 "@tsconfig/recommended": ^1.0.3 @@ -11833,7 +11833,7 @@ __metadata: "@google/generative-ai": ^0.7.0 "@jest/globals": ^29.5.0 "@langchain/core": ">=0.2.21 <0.3.0" - "@langchain/scripts": ^0.0.21 + "@langchain/scripts": ^0.0.22 "@langchain/standard-tests": 0.0.0 "@swc/core": ^1.3.90 "@swc/jest": ^0.2.29 @@ -11869,7 +11869,7 @@ __metadata: "@langchain/core": ">=0.2.21 <0.3.0" "@langchain/google-common": ~0.0 "@langchain/google-webauth": ~0.0.26 - "@langchain/scripts": ^0.0.21 + "@langchain/scripts": ^0.0.22 "@langchain/standard-tests": 0.0.0 "@swc/core": ^1.3.90 "@swc/jest": ^0.2.29 @@ -11903,7 +11903,7 @@ __metadata: "@langchain/core": ">=0.2.21 <0.3.0" "@langchain/google-common": ~0.0 "@langchain/google-gauth": ~0.0.26 - "@langchain/scripts": ^0.0.21 + "@langchain/scripts": ^0.0.22 "@langchain/standard-tests": 0.0.0 "@swc/core": ^1.3.90 "@swc/jest": ^0.2.29 @@ -11936,7 +11936,7 @@ __metadata: "@jest/globals": ^29.5.0 "@langchain/core": ">=0.2.21 <0.3.0" "@langchain/google-common": ~0.0.26 - "@langchain/scripts": ^0.0.21 + "@langchain/scripts": ^0.0.22 "@swc/core": ^1.3.90 "@swc/jest": ^0.2.29 "@tsconfig/recommended": ^1.0.3 @@ -11981,7 +11981,7 @@ __metadata: "@jest/globals": ^29.5.0 "@langchain/core": ">=0.2.21 <0.3.0" "@langchain/openai": "workspace:^" - "@langchain/scripts": ^0.0.21 + "@langchain/scripts": ^0.0.22 "@langchain/standard-tests": 0.0.0 "@swc/core": ^1.3.90 "@swc/jest": ^0.2.29 @@ -12077,7 +12077,7 @@ __metadata: dependencies: "@jest/globals": ^29.5.0 "@langchain/core": ">=0.2.21 <0.3.0" - "@langchain/scripts": ^0.0.21 + "@langchain/scripts": ^0.0.22 "@langchain/standard-tests": 0.0.0 "@mistralai/mistralai": ^1.1.0 "@swc/core": ^1.3.90 @@ -12112,7 +12112,7 @@ __metadata: dependencies: "@jest/globals": ^29.5.0 "@langchain/core": ">=0.2.5 <0.3.0" - "@langchain/scripts": ^0.0.21 + "@langchain/scripts": ^0.0.22 "@langchain/standard-tests": 0.0.0 "@mixedbread-ai/sdk": ^2.2.3 "@swc/core": ^1.3.90 @@ -12146,7 +12146,7 @@ __metadata: "@jest/globals": ^29.5.0 "@langchain/core": ">0.1.0 <0.3.0" "@langchain/openai": "workspace:*" - "@langchain/scripts": ^0.0.21 + "@langchain/scripts": ^0.0.22 "@swc/core": ^1.3.90 "@swc/jest": ^0.2.29 "@tsconfig/recommended": ^1.0.3 @@ -12180,7 +12180,7 @@ __metadata: "@jest/globals": ^29.5.0 "@langchain/core": ">0.1.0 <0.3.0" "@langchain/openai": "workspace:^" - "@langchain/scripts": ^0.0.21 + "@langchain/scripts": ^0.0.22 "@nomic-ai/atlas": ^0.8.0 "@swc/core": ^1.3.90 "@swc/jest": ^0.2.29 @@ -12223,7 +12223,7 @@ __metadata: dependencies: "@jest/globals": ^29.5.0 "@langchain/core": ">=0.2.21 <0.3.0" - "@langchain/scripts": ^0.0.21 + "@langchain/scripts": ^0.0.22 "@langchain/standard-tests": 0.0.0 "@swc/core": ^1.3.90 "@swc/jest": ^0.2.29 @@ -12259,7 +12259,7 @@ __metadata: "@azure/identity": ^4.2.1 "@jest/globals": ^29.5.0 "@langchain/core": ">=0.2.26 <0.3.0" - "@langchain/scripts": ^0.0.21 + "@langchain/scripts": ^0.0.22 "@langchain/standard-tests": 0.0.0 "@swc/core": ^1.3.90 "@swc/jest": ^0.2.29 @@ -12306,7 +12306,7 @@ __metadata: "@jest/globals": ^29.5.0 "@langchain/core": ">0.2.0 <0.3.0" "@langchain/openai": "workspace:*" - "@langchain/scripts": ^0.0.21 + "@langchain/scripts": ^0.0.22 "@pinecone-database/pinecone": ^3.0.0 "@swc/core": ^1.3.90 "@swc/jest": ^0.2.29 @@ -12341,7 +12341,7 @@ __metadata: "@faker-js/faker": ^8.4.1 "@jest/globals": ^29.5.0 "@langchain/core": ">0.1.0 <0.3.0" - "@langchain/scripts": ^0.0.21 + "@langchain/scripts": ^0.0.22 "@qdrant/js-client-rest": ^1.9.0 "@swc/core": ^1.3.90 "@swc/jest": ^0.2.29 @@ -12375,7 +12375,7 @@ __metadata: "@faker-js/faker": ^8.4.0 "@jest/globals": ^29.5.0 "@langchain/core": ">0.1.0 <0.3.0" - "@langchain/scripts": ^0.0.21 + "@langchain/scripts": ^0.0.22 "@swc/core": ^1.3.90 "@swc/jest": ^0.2.29 "@tsconfig/recommended": ^1.0.3 @@ -12402,29 +12402,7 @@ __metadata: languageName: unknown linkType: soft -"@langchain/scripts@npm:^0.0.21": - version: 0.0.21 - resolution: "@langchain/scripts@npm:0.0.21" - dependencies: - "@rollup/wasm-node": ^4.19.0 - axios: ^1.6.7 - commander: ^11.1.0 - glob: ^10.3.10 - lodash: ^4.17.21 - readline: ^1.3.0 - rimraf: ^5.0.1 - rollup: ^4.5.2 - ts-morph: ^21.0.1 - typescript: ^5.4.5 - bin: - lc-build: bin/build.js - lc_build_v2: bin/build_v2.js - notebook_validate: bin/validate_notebook.js - checksum: 4653f3e5866d5c206013eb3d556bd11cc410e5ad118ed62f3794a3a63de8fc56bab362c537700387a03cf5aee018fc117413c3d6048d50ed9a54adc07006d13c - languageName: node - linkType: hard - -"@langchain/scripts@workspace:*, @langchain/scripts@workspace:libs/langchain-scripts, @langchain/scripts@~0.0": +"@langchain/scripts@^0.0.22, @langchain/scripts@workspace:*, @langchain/scripts@workspace:libs/langchain-scripts, @langchain/scripts@~0.0": version: 0.0.0-use.local resolution: "@langchain/scripts@workspace:libs/langchain-scripts" dependencies: @@ -12504,7 +12482,7 @@ __metadata: dependencies: "@jest/globals": ^29.5.0 "@langchain/core": ">0.2.0 <0.3.0" - "@langchain/scripts": ^0.0.21 + "@langchain/scripts": ^0.0.22 "@swc/core": ^1.3.90 "@swc/jest": ^0.2.29 "@tsconfig/recommended": ^1.0.3 @@ -12536,7 +12514,7 @@ __metadata: "@jest/globals": ^29.5.0 "@langchain/core": ">0.2.0 <0.3.0" "@langchain/openai": "workspace:^" - "@langchain/scripts": ^0.0.21 + "@langchain/scripts": ^0.0.22 "@swc/core": ^1.3.90 "@swc/jest": ^0.2.29 "@tsconfig/recommended": ^1.0.3 @@ -12570,7 +12548,7 @@ __metadata: dependencies: "@jest/globals": ^29.5.0 "@langchain/core": ">0.1.0 <0.3.0" - "@langchain/scripts": ^0.0.21 + "@langchain/scripts": ^0.0.22 "@swc/core": ^1.3.90 "@swc/jest": ^0.2.29 "@tsconfig/recommended": ^1.0.3 @@ -26715,7 +26693,7 @@ __metadata: "@langchain/pinecone": "workspace:*" "@langchain/qdrant": "workspace:*" "@langchain/redis": "workspace:*" - "@langchain/scripts": ^0.0.21 + "@langchain/scripts": ^0.0.22 "@langchain/textsplitters": "workspace:*" "@langchain/weaviate": "workspace:*" "@langchain/yandex": "workspace:*" @@ -32080,7 +32058,7 @@ __metadata: "@langchain/mistralai": ^0.0.26 "@langchain/ollama": ^0.0.2 "@langchain/openai": ">=0.1.0 <0.3.0" - "@langchain/scripts": ^0.0.21 + "@langchain/scripts": ^0.0.22 "@langchain/textsplitters": ~0.0.0 "@mendable/firecrawl-js": ^0.0.13 "@notionhq/client": ^2.2.10 From f598c5d86f514f07d42445961aadb4e21efe0017 Mon Sep 17 00:00:00 2001 From: Brace Sproul Date: Wed, 21 Aug 2024 11:55:57 -0700 Subject: [PATCH 068/344] docs[patch]: Add ls example selector link to how to index (#6588) --- docs/core_docs/docs/how_to/index.mdx | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/core_docs/docs/how_to/index.mdx b/docs/core_docs/docs/how_to/index.mdx index a0459f3a2e21..e9f756ab7953 100644 --- a/docs/core_docs/docs/how_to/index.mdx +++ b/docs/core_docs/docs/how_to/index.mdx @@ -62,6 +62,7 @@ These are the core building blocks you can use when building applications. - [How to: use example selectors](/docs/how_to/example_selectors) - [How to: select examples by length](/docs/how_to/example_selectors_length_based) - [How to: select examples by semantic similarity](/docs/how_to/example_selectors_similarity) +- [How to: select examples from LangSmith few-shot datasets](/docs/how_to/example_selectors_langsmith) ### Chat models From f3e2379fecf357ff26918f9944041d1cf7e8c926 Mon Sep 17 00:00:00 2001 From: Brace Sproul Date: Wed, 21 Aug 2024 12:20:54 -0700 Subject: [PATCH 069/344] docs[minor]: Port few shot concepts (#6589) * docs[minor]: Port few shot concepts * chore: lint files --- docs/core_docs/docs/concepts.mdx | 95 ++++++++++++++++++++++++++++++-- 1 file changed, 90 insertions(+), 5 deletions(-) diff --git a/docs/core_docs/docs/concepts.mdx b/docs/core_docs/docs/concepts.mdx index f363cf44fd46..e751a8db1e02 100644 --- a/docs/core_docs/docs/concepts.mdx +++ b/docs/core_docs/docs/concepts.mdx @@ -206,7 +206,7 @@ Some language models take an array of messages as input and return a message. There are a few different types of messages. All messages have a `role`, `content`, and `response_metadata` property. -The `role` describes WHO is saying the message. +The `role` describes WHO is saying the message. The standard roles are "user", "assistant", "system", and "tool". LangChain has different message classes for different roles. The `content` property describes the content of the message. @@ -215,13 +215,16 @@ This can be a few different things: - A string (most models deal this type of content) - A List of objects (this is used for multi-modal input, where the object contains information about that input type and that input location) +Optionally, messages can have a `name` property which allows for differentiating between multiple speakers with the same role. +For example, if there are two users in the chat history it can be useful to differentiate between them. Not all models support this. + #### HumanMessage -This represents a message from the user. +This represents a message with role "user". #### AIMessage -This represents a message from the model. In addition to the `content` property, these messages also have: +This represents a message with role "assistant". In addition to the `content` property, these messages also have: **`response_metadata`** @@ -241,11 +244,11 @@ This property returns a list of `ToolCall`s. A `ToolCall` is an object with the #### SystemMessage -This represents a system message, which tells the model how to behave. Not every model provider supports this. +This represents a message with role "system", which tells the model how to behave. Not every model provider supports this. #### ToolMessage -This represents the result of a tool call. In addition to `role` and `content`, this message has: +This represents a message with role "tool", which contains the result of calling a tool. In addition to `role` and `content`, this message has: - a `tool_call_id` field which conveys the id of the call to the tool that was called to produce this result. - an `artifact` field which can be used to pass along arbitrary artifacts of the tool execution which are useful to track but which should not be sent to the model. @@ -347,6 +350,7 @@ For specifics on how to use prompt templates, see the [relevant how-to guides he ### Example Selectors One common prompting technique for achieving better performance is to include examples as part of the prompt. +This is known as [few-shot prompting](/docs/concepts/#few-shot-prompting). This gives the language model concrete examples of how it should behave. Sometimes these examples are hardcoded into the prompt, but for more advanced situations it may be nice to dynamically select them. Example Selectors are classes responsible for selecting and then formatting examples into prompts. @@ -1144,6 +1148,87 @@ The following how-to guides are good practical resources for using function/tool - [How to return structured data from an LLM](/docs/how_to/structured_output/) - [How to use a model to call tools](/docs/how_to/tool_calling) +### Few-shot prompting + +One of the most effective ways to improve model performance is to give a model examples of what you want it to do. The technique of adding example inputs and expected outputs to a model prompt is known as "few-shot prompting". There are a few things to think about when doing few-shot prompting: + +1. How are examples generated? +2. How many examples are in each prompt? +3. How are examples selected at runtime? +4. How are examples formatted in the prompt? + +Here are the considerations for each. + +#### 1. Generating examples + +The first and most important step of few-shot prompting is coming up with a good dataset of examples. +Good examples should be relevant at runtime, clear, informative, and provide information that was not already known to the model. + +At a high-level, the basic ways to generate examples are: + +- Manual: a person/people generates examples they think are useful. +- Better model: a better (presumably more expensive/slower) model's responses are used as examples for a worse (presumably cheaper/faster) model. +- User feedback: users (or labelers) leave feedback on interactions with the application and examples are generated based on that feedback (for example, all interactions with positive feedback could be turned into examples). +- LLM feedback: same as user feedback but the process is automated by having models evaluate themselves. + +Which approach is best depends on your task. For tasks where a small number core principles need to be understood really well, it can be valuable hand-craft a few really good examples. +For tasks where the space of correct behaviors is broader and more nuanced, it can be useful to generate many examples in a more automated fashion so that there's a higher likelihood of there being some highly relevant examples for any runtime input. + +**Single-turn v.s. multi-turn examples** + +Another dimension to think about when generating examples is what the example is actually showing. + +The simplest types of examples just have a user input and an expected model output. These are single-turn examples. + +One more complex type if example is where the example is an entire conversation, usually in which a model initially responds incorrectly and a user then tells the model how to correct its answer. +This is called a multi-turn example. Multi-turn examples can be useful for more nuanced tasks where its useful to show common errors and spell out exactly why they're wrong and what should be done instead. + +#### 2. Number of examples + +Once we have a dataset of examples, we need to think about how many examples should be in each prompt. +The key tradeoff is that more examples generally improve performance, but larger prompts increase costs and latency. +And beyond some threshold having too many examples can start to confuse the model. +Finding the right number of examples is highly dependent on the model, the task, the quality of the examples, and your cost and latency constraints. +Anecdotally, the better the model is the fewer examples it needs to perform well and the more quickly you hit steeply diminishing returns on adding more examples. +But, the best/only way to reliably answer this question is to run some experiments with different numbers of examples. + +#### 3. Selecting examples + +Assuming we are not adding our entire example dataset into each prompt, we need to have a way of selecting examples from our dataset based on a given input. We can do this: + +- Randomly +- By (semantic or keyword-based) similarity of the inputs +- Based on some other constraints, like token size + +LangChain has a number of [`ExampleSelectors`](/docs/concepts/#example-selectors) which make it easy to use any of these techniques. + +Generally, selecting by semantic similarity leads to the best model performance. But how important this is is again model and task specific, and is something worth experimenting with. + +#### 4. Formatting examples + +Most state-of-the-art models these days are chat models, so we'll focus on formatting examples for those. Our basic options are to insert the examples: + +- In the system prompt as a string +- As their own messages + +If we insert our examples into the system prompt as a string, we'll need to make sure it's clear to the model where each example begins and which parts are the input versus output. Different models respond better to different syntaxes, like [ChatML](https://learn.microsoft.com/en-us/azure/ai-services/openai/how-to/chat-markup-language), XML, TypeScript, etc. + +If we insert our examples as messages, where each example is represented as a sequence of Human, AI messages, we might want to also assign [names](/docs/concepts/#messages) to our messages like `"exampleUser"` and `"exampleAssistant"` to make it clear that these messages correspond to different actors than the latest input message. + +**Formatting tool call examples** + +One area where formatting examples as messages can be tricky is when our example outputs have tool calls. This is because different models have different constraints on what types of message sequences are allowed when any tool calls are generated. + +- Some models require that any AIMessage with tool calls be immediately followed by ToolMessages for every tool call, +- Some models additionally require that any ToolMessages be immediately followed by an AIMessage before the next HumanMessage, +- Some models require that tools are passed in to the model if there are any tool calls / ToolMessages in the chat history. + +These requirements are model-specific and should be checked for the model you are using. If your model requires ToolMessages after tool calls and/or AIMessages after ToolMessages and your examples only include expected tool calls and not the actual tool outputs, you can try adding dummy ToolMessages / AIMessages to the end of each example with generic contents to satisfy the API constraints. + +In these cases it's especially worth experimenting with inserting your examples as strings versus messages, as having dummy messages can adversely affect certain models. + +You can see a case study of how Anthropic and OpenAI respond to different few-shot prompting techniques on two different tool calling benchmarks [here](https://blog.langchain.dev/few-shot-prompting-to-improve-tool-calling-performance/). + ### Retrieval LLMs are trained on a large but fixed dataset, limiting their ability to reason over private or recent information. Fine-tuning an LLM with specific facts is one way to mitigate this, but is often [poorly suited for factual recall](https://www.anyscale.com/blog/fine-tuning-is-for-form-not-facts) and [can be costly](https://www.glean.com/blog/how-to-build-an-ai-assistant-for-the-enterprise). From 0ba524ebdeed00bb32396d94afb670c31ec609d6 Mon Sep 17 00:00:00 2001 From: Brace Sproul Date: Wed, 21 Aug 2024 16:20:42 -0700 Subject: [PATCH 070/344] scripts[patch]: Fix reading original entrypoint file (#6591) * scripts[patch]: Fix reading original entrypoint file * chore: lint files --- libs/langchain-scripts/src/build_v2.ts | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/libs/langchain-scripts/src/build_v2.ts b/libs/langchain-scripts/src/build_v2.ts index 13fc67fbb863..4c4b42653c60 100644 --- a/libs/langchain-scripts/src/build_v2.ts +++ b/libs/langchain-scripts/src/build_v2.ts @@ -490,17 +490,13 @@ function listEntrypoints(packageJson: Record) { const checkAllowSideEffects = async (entrypoint: string): Promise => { let entrypointContent: Buffer | undefined; try { - entrypointContent = await fs.promises.readFile( - `./dist/${entrypoint.replace(/^\.\//, "")}` - ); + entrypointContent = await fs.promises.readFile(`./dist/${entrypoint}.js`); // eslint-disable-next-line @typescript-eslint/no-explicit-any } catch (e: any) { if (e.message.includes("ENOENT")) { // Entrypoint is likely via an `index.js` file, retry with `index.js` appended to path entrypointContent = await fs.promises.readFile( - `./dist/${entrypoint - .replace(/^\.\//, "") - .replace(/\.js$/, "")}/index.js` + `./dist/${entrypoint}/index.js` ); } } @@ -542,7 +538,12 @@ async function checkTreeShaking(config: LangChainConfig) { let hasUnexpectedSideEffects = sideEffects.length > 0; if (hasUnexpectedSideEffects) { - hasUnexpectedSideEffects = !(await checkAllowSideEffects(entrypoint)); + // Map the entrypoint back to the actual file entrypoint using the LangChainConfig file + const actualEntrypoint = + config.entrypoints[entrypoint.replace(/^\.\/|\.js$/g, "")]; + hasUnexpectedSideEffects = !(await checkAllowSideEffects( + actualEntrypoint + )); } reportMap.set(entrypoint, { log: sideEffects, From 4350b27a4e363d9ef2db42b9723d72c5d9b567b3 Mon Sep 17 00:00:00 2001 From: Brace Sproul Date: Wed, 21 Aug 2024 16:23:46 -0700 Subject: [PATCH 071/344] scripts[patch]: Release 0.0.23 (#6592) * scripts[patch]: Release 0.0.23 * bump scripts version --- examples/package.json | 2 +- langchain-core/package.json | 2 +- langchain/package.json | 2 +- .../template/package.json | 2 +- libs/langchain-anthropic/package.json | 2 +- libs/langchain-aws/package.json | 2 +- .../package.json | 2 +- libs/langchain-azure-openai/package.json | 2 +- libs/langchain-baidu-qianfan/package.json | 2 +- libs/langchain-cloudflare/package.json | 2 +- libs/langchain-cohere/package.json | 2 +- libs/langchain-community/package.json | 2 +- libs/langchain-exa/package.json | 2 +- libs/langchain-google-common/package.json | 2 +- libs/langchain-google-gauth/package.json | 2 +- libs/langchain-google-genai/package.json | 2 +- .../package.json | 2 +- libs/langchain-google-vertexai/package.json | 2 +- libs/langchain-google-webauth/package.json | 2 +- libs/langchain-groq/package.json | 2 +- libs/langchain-mistralai/package.json | 2 +- libs/langchain-mixedbread-ai/package.json | 2 +- libs/langchain-mongodb/package.json | 2 +- libs/langchain-nomic/package.json | 2 +- libs/langchain-ollama/package.json | 2 +- libs/langchain-openai/package.json | 2 +- libs/langchain-pinecone/package.json | 2 +- libs/langchain-qdrant/package.json | 2 +- libs/langchain-redis/package.json | 2 +- libs/langchain-scripts/package.json | 2 +- libs/langchain-textsplitters/package.json | 2 +- libs/langchain-weaviate/package.json | 2 +- libs/langchain-yandex/package.json | 2 +- yarn.lock | 64 +++++++++---------- 34 files changed, 65 insertions(+), 65 deletions(-) diff --git a/examples/package.json b/examples/package.json index a109009b46a2..340405e6b8ad 100644 --- a/examples/package.json +++ b/examples/package.json @@ -58,7 +58,7 @@ "@langchain/pinecone": "workspace:*", "@langchain/qdrant": "workspace:*", "@langchain/redis": "workspace:*", - "@langchain/scripts": "^0.0.22", + "@langchain/scripts": "^0.0.23", "@langchain/textsplitters": "workspace:*", "@langchain/weaviate": "workspace:*", "@langchain/yandex": "workspace:*", diff --git a/langchain-core/package.json b/langchain-core/package.json index abe896a07b17..613cb1522b01 100644 --- a/langchain-core/package.json +++ b/langchain-core/package.json @@ -55,7 +55,7 @@ }, "devDependencies": { "@jest/globals": "^29.5.0", - "@langchain/scripts": "^0.0.22", + "@langchain/scripts": "^0.0.23", "@swc/core": "^1.3.90", "@swc/jest": "^0.2.29", "@types/decamelize": "^1.2.0", diff --git a/langchain/package.json b/langchain/package.json index 6263efc4bf71..fe3c6ee0b599 100644 --- a/langchain/package.json +++ b/langchain/package.json @@ -621,7 +621,7 @@ "@langchain/groq": "^0.0.15", "@langchain/mistralai": "^0.0.26", "@langchain/ollama": "^0.0.2", - "@langchain/scripts": "^0.0.22", + "@langchain/scripts": "^0.0.23", "@mendable/firecrawl-js": "^0.0.13", "@notionhq/client": "^2.2.10", "@pinecone-database/pinecone": "^1.1.0", diff --git a/libs/create-langchain-integration/template/package.json b/libs/create-langchain-integration/template/package.json index 5b5b48eafe24..4b3cb068bf24 100644 --- a/libs/create-langchain-integration/template/package.json +++ b/libs/create-langchain-integration/template/package.json @@ -45,7 +45,7 @@ "@jest/globals": "^29.5.0", "@swc/core": "^1.3.90", "@swc/jest": "^0.2.29", - "@langchain/scripts": "^0.0.22", + "@langchain/scripts": "^0.0.23", "@tsconfig/recommended": "^1.0.3", "@typescript-eslint/eslint-plugin": "^6.12.0", "@typescript-eslint/parser": "^6.12.0", diff --git a/libs/langchain-anthropic/package.json b/libs/langchain-anthropic/package.json index 5bc74d744bba..f4ef0778ee65 100644 --- a/libs/langchain-anthropic/package.json +++ b/libs/langchain-anthropic/package.json @@ -43,7 +43,7 @@ }, "devDependencies": { "@jest/globals": "^29.5.0", - "@langchain/scripts": "^0.0.22", + "@langchain/scripts": "^0.0.23", "@langchain/standard-tests": "0.0.0", "@swc/core": "^1.3.90", "@swc/jest": "^0.2.29", diff --git a/libs/langchain-aws/package.json b/libs/langchain-aws/package.json index 1d4be50baed5..d9a4990e1146 100644 --- a/libs/langchain-aws/package.json +++ b/libs/langchain-aws/package.json @@ -50,7 +50,7 @@ "devDependencies": { "@aws-sdk/types": "^3.609.0", "@jest/globals": "^29.5.0", - "@langchain/scripts": "^0.0.22", + "@langchain/scripts": "^0.0.23", "@langchain/standard-tests": "0.0.0", "@smithy/types": "^3.2.0", "@swc/core": "^1.3.90", diff --git a/libs/langchain-azure-dynamic-sessions/package.json b/libs/langchain-azure-dynamic-sessions/package.json index c503fcd5f74c..c726d00e93cf 100644 --- a/libs/langchain-azure-dynamic-sessions/package.json +++ b/libs/langchain-azure-dynamic-sessions/package.json @@ -44,7 +44,7 @@ }, "devDependencies": { "@jest/globals": "^29.5.0", - "@langchain/scripts": "^0.0.22", + "@langchain/scripts": "^0.0.23", "@swc/core": "^1.3.90", "@swc/jest": "^0.2.29", "@tsconfig/recommended": "^1.0.3", diff --git a/libs/langchain-azure-openai/package.json b/libs/langchain-azure-openai/package.json index 0899a8d8b698..26631a07a7d8 100644 --- a/libs/langchain-azure-openai/package.json +++ b/libs/langchain-azure-openai/package.json @@ -41,7 +41,7 @@ "devDependencies": { "@azure/identity": "^4.2.1", "@jest/globals": "^29.5.0", - "@langchain/scripts": "^0.0.22", + "@langchain/scripts": "^0.0.23", "@langchain/standard-tests": "0.0.0", "@swc/core": "^1.3.90", "@swc/jest": "^0.2.29", diff --git a/libs/langchain-baidu-qianfan/package.json b/libs/langchain-baidu-qianfan/package.json index 3fa2d51c7627..50d9b678f926 100644 --- a/libs/langchain-baidu-qianfan/package.json +++ b/libs/langchain-baidu-qianfan/package.json @@ -40,7 +40,7 @@ "devDependencies": { "@jest/globals": "^29.5.0", "@langchain/openai": "~0.1.0", - "@langchain/scripts": "^0.0.22", + "@langchain/scripts": "^0.0.23", "@swc/core": "^1.3.90", "@swc/jest": "^0.2.29", "@tsconfig/recommended": "^1.0.3", diff --git a/libs/langchain-cloudflare/package.json b/libs/langchain-cloudflare/package.json index 7633679dde7d..45bd5752740e 100644 --- a/libs/langchain-cloudflare/package.json +++ b/libs/langchain-cloudflare/package.json @@ -43,7 +43,7 @@ "@cloudflare/workers-types": "^4.20231218.0", "@jest/globals": "^29.5.0", "@langchain/langgraph": "~0.0.31", - "@langchain/scripts": "^0.0.22", + "@langchain/scripts": "^0.0.23", "@langchain/standard-tests": "0.0.0", "@swc/core": "^1.3.90", "@swc/jest": "^0.2.29", diff --git a/libs/langchain-cohere/package.json b/libs/langchain-cohere/package.json index 436b0c776b87..634afa79e05a 100644 --- a/libs/langchain-cohere/package.json +++ b/libs/langchain-cohere/package.json @@ -43,7 +43,7 @@ }, "devDependencies": { "@jest/globals": "^29.5.0", - "@langchain/scripts": "^0.0.22", + "@langchain/scripts": "^0.0.23", "@langchain/standard-tests": "0.0.0", "@swc/core": "^1.3.90", "@swc/jest": "^0.2.29", diff --git a/libs/langchain-community/package.json b/libs/langchain-community/package.json index 63e8945eea79..697618778bf8 100644 --- a/libs/langchain-community/package.json +++ b/libs/langchain-community/package.json @@ -79,7 +79,7 @@ "@huggingface/inference": "^2.6.4", "@jest/globals": "^29.5.0", "@langchain/langgraph": "<0.1.0", - "@langchain/scripts": "^0.0.22", + "@langchain/scripts": "^0.0.23", "@langchain/standard-tests": "0.0.0", "@layerup/layerup-security": "^1.5.12", "@mendable/firecrawl-js": "^0.0.36", diff --git a/libs/langchain-exa/package.json b/libs/langchain-exa/package.json index 7144cbab2ae9..6f112e3223a1 100644 --- a/libs/langchain-exa/package.json +++ b/libs/langchain-exa/package.json @@ -45,7 +45,7 @@ }, "devDependencies": { "@jest/globals": "^29.5.0", - "@langchain/scripts": "^0.0.22", + "@langchain/scripts": "^0.0.23", "@swc/core": "^1.3.90", "@swc/jest": "^0.2.29", "@tsconfig/recommended": "^1.0.3", diff --git a/libs/langchain-google-common/package.json b/libs/langchain-google-common/package.json index 6fad85806da3..0d96e938a1db 100644 --- a/libs/langchain-google-common/package.json +++ b/libs/langchain-google-common/package.json @@ -46,7 +46,7 @@ }, "devDependencies": { "@jest/globals": "^29.5.0", - "@langchain/scripts": "^0.0.22", + "@langchain/scripts": "^0.0.23", "@swc/core": "^1.3.90", "@swc/jest": "^0.2.29", "@tsconfig/recommended": "^1.0.3", diff --git a/libs/langchain-google-gauth/package.json b/libs/langchain-google-gauth/package.json index b4e6b33e2ec2..0fea7767dba0 100644 --- a/libs/langchain-google-gauth/package.json +++ b/libs/langchain-google-gauth/package.json @@ -41,7 +41,7 @@ }, "devDependencies": { "@jest/globals": "^29.5.0", - "@langchain/scripts": "^0.0.22", + "@langchain/scripts": "^0.0.23", "@swc/core": "^1.3.90", "@swc/jest": "^0.2.29", "@tsconfig/recommended": "^1.0.3", diff --git a/libs/langchain-google-genai/package.json b/libs/langchain-google-genai/package.json index ac6bbda0e9c2..dd54061733df 100644 --- a/libs/langchain-google-genai/package.json +++ b/libs/langchain-google-genai/package.json @@ -41,7 +41,7 @@ }, "devDependencies": { "@jest/globals": "^29.5.0", - "@langchain/scripts": "^0.0.22", + "@langchain/scripts": "^0.0.23", "@langchain/standard-tests": "0.0.0", "@swc/core": "^1.3.90", "@swc/jest": "^0.2.29", diff --git a/libs/langchain-google-vertexai-web/package.json b/libs/langchain-google-vertexai-web/package.json index f63b59e797fd..13fec9df09b7 100644 --- a/libs/langchain-google-vertexai-web/package.json +++ b/libs/langchain-google-vertexai-web/package.json @@ -46,7 +46,7 @@ "devDependencies": { "@jest/globals": "^29.5.0", "@langchain/google-common": "~0.0", - "@langchain/scripts": "^0.0.22", + "@langchain/scripts": "^0.0.23", "@langchain/standard-tests": "0.0.0", "@swc/core": "^1.3.90", "@swc/jest": "^0.2.29", diff --git a/libs/langchain-google-vertexai/package.json b/libs/langchain-google-vertexai/package.json index 92f98bb8db0c..5a0faf98f3b7 100644 --- a/libs/langchain-google-vertexai/package.json +++ b/libs/langchain-google-vertexai/package.json @@ -49,7 +49,7 @@ "devDependencies": { "@jest/globals": "^29.5.0", "@langchain/google-common": "~0.0", - "@langchain/scripts": "^0.0.22", + "@langchain/scripts": "^0.0.23", "@langchain/standard-tests": "0.0.0", "@swc/core": "^1.3.90", "@swc/jest": "^0.2.29", diff --git a/libs/langchain-google-webauth/package.json b/libs/langchain-google-webauth/package.json index 4e2d1072a0ad..e427d62c0ddd 100644 --- a/libs/langchain-google-webauth/package.json +++ b/libs/langchain-google-webauth/package.json @@ -46,7 +46,7 @@ }, "devDependencies": { "@jest/globals": "^29.5.0", - "@langchain/scripts": "^0.0.22", + "@langchain/scripts": "^0.0.23", "@swc/core": "^1.3.90", "@swc/jest": "^0.2.29", "@tsconfig/recommended": "^1.0.3", diff --git a/libs/langchain-groq/package.json b/libs/langchain-groq/package.json index f2648b4f3a9e..42c79b7b73fd 100644 --- a/libs/langchain-groq/package.json +++ b/libs/langchain-groq/package.json @@ -44,7 +44,7 @@ "devDependencies": { "@jest/globals": "^29.5.0", "@langchain/openai": "workspace:^", - "@langchain/scripts": "^0.0.22", + "@langchain/scripts": "^0.0.23", "@langchain/standard-tests": "0.0.0", "@swc/core": "^1.3.90", "@swc/jest": "^0.2.29", diff --git a/libs/langchain-mistralai/package.json b/libs/langchain-mistralai/package.json index 121e9f21092c..c92f0fa42432 100644 --- a/libs/langchain-mistralai/package.json +++ b/libs/langchain-mistralai/package.json @@ -43,7 +43,7 @@ }, "devDependencies": { "@jest/globals": "^29.5.0", - "@langchain/scripts": "^0.0.22", + "@langchain/scripts": "^0.0.23", "@langchain/standard-tests": "0.0.0", "@swc/core": "^1.3.90", "@swc/jest": "^0.2.29", diff --git a/libs/langchain-mixedbread-ai/package.json b/libs/langchain-mixedbread-ai/package.json index 5317646d976c..68d207ac8877 100644 --- a/libs/langchain-mixedbread-ai/package.json +++ b/libs/langchain-mixedbread-ai/package.json @@ -40,7 +40,7 @@ }, "devDependencies": { "@jest/globals": "^29.5.0", - "@langchain/scripts": "^0.0.22", + "@langchain/scripts": "^0.0.23", "@langchain/standard-tests": "0.0.0", "@swc/core": "^1.3.90", "@swc/jest": "^0.2.29", diff --git a/libs/langchain-mongodb/package.json b/libs/langchain-mongodb/package.json index 91c26fb525d1..cd526c859df5 100644 --- a/libs/langchain-mongodb/package.json +++ b/libs/langchain-mongodb/package.json @@ -46,7 +46,7 @@ "devDependencies": { "@jest/globals": "^29.5.0", "@langchain/openai": "workspace:*", - "@langchain/scripts": "^0.0.22", + "@langchain/scripts": "^0.0.23", "@swc/core": "^1.3.90", "@swc/jest": "^0.2.29", "@tsconfig/recommended": "^1.0.3", diff --git a/libs/langchain-nomic/package.json b/libs/langchain-nomic/package.json index 2168b199c976..76daa46b574b 100644 --- a/libs/langchain-nomic/package.json +++ b/libs/langchain-nomic/package.json @@ -46,7 +46,7 @@ "devDependencies": { "@jest/globals": "^29.5.0", "@langchain/openai": "workspace:^", - "@langchain/scripts": "^0.0.22", + "@langchain/scripts": "^0.0.23", "@swc/core": "^1.3.90", "@swc/jest": "^0.2.29", "@tsconfig/recommended": "^1.0.3", diff --git a/libs/langchain-ollama/package.json b/libs/langchain-ollama/package.json index c008be49ca9c..88d5c74d7917 100644 --- a/libs/langchain-ollama/package.json +++ b/libs/langchain-ollama/package.json @@ -41,7 +41,7 @@ }, "devDependencies": { "@jest/globals": "^29.5.0", - "@langchain/scripts": "^0.0.22", + "@langchain/scripts": "^0.0.23", "@langchain/standard-tests": "0.0.0", "@swc/core": "^1.3.90", "@swc/jest": "^0.2.29", diff --git a/libs/langchain-openai/package.json b/libs/langchain-openai/package.json index 738b6a76e84e..4ba163dd8703 100644 --- a/libs/langchain-openai/package.json +++ b/libs/langchain-openai/package.json @@ -44,7 +44,7 @@ "devDependencies": { "@azure/identity": "^4.2.1", "@jest/globals": "^29.5.0", - "@langchain/scripts": "^0.0.22", + "@langchain/scripts": "^0.0.23", "@langchain/standard-tests": "0.0.0", "@swc/core": "^1.3.90", "@swc/jest": "^0.2.29", diff --git a/libs/langchain-pinecone/package.json b/libs/langchain-pinecone/package.json index 12cc7eaec7ba..702a8ff3d590 100644 --- a/libs/langchain-pinecone/package.json +++ b/libs/langchain-pinecone/package.json @@ -48,7 +48,7 @@ "@faker-js/faker": "^8.3.1", "@jest/globals": "^29.5.0", "@langchain/openai": "workspace:*", - "@langchain/scripts": "^0.0.22", + "@langchain/scripts": "^0.0.23", "@swc/core": "^1.3.90", "@swc/jest": "^0.2.29", "@tsconfig/recommended": "^1.0.3", diff --git a/libs/langchain-qdrant/package.json b/libs/langchain-qdrant/package.json index a8bd1aefa31e..40548cfe9e44 100644 --- a/libs/langchain-qdrant/package.json +++ b/libs/langchain-qdrant/package.json @@ -46,7 +46,7 @@ "devDependencies": { "@faker-js/faker": "^8.4.1", "@jest/globals": "^29.5.0", - "@langchain/scripts": "^0.0.22", + "@langchain/scripts": "^0.0.23", "@swc/core": "^1.3.90", "@swc/jest": "^0.2.29", "@tsconfig/recommended": "^1.0.3", diff --git a/libs/langchain-redis/package.json b/libs/langchain-redis/package.json index 1903dead9b03..fda7d74d59a6 100644 --- a/libs/langchain-redis/package.json +++ b/libs/langchain-redis/package.json @@ -45,7 +45,7 @@ "devDependencies": { "@faker-js/faker": "^8.4.0", "@jest/globals": "^29.5.0", - "@langchain/scripts": "^0.0.22", + "@langchain/scripts": "^0.0.23", "@swc/core": "^1.3.90", "@swc/jest": "^0.2.29", "@tsconfig/recommended": "^1.0.3", diff --git a/libs/langchain-scripts/package.json b/libs/langchain-scripts/package.json index 16bd6973c053..bd069a48e35f 100644 --- a/libs/langchain-scripts/package.json +++ b/libs/langchain-scripts/package.json @@ -1,6 +1,6 @@ { "name": "@langchain/scripts", - "version": "0.0.22", + "version": "0.0.23", "description": "Shared scripts for LangChain.js", "type": "module", "engines": { diff --git a/libs/langchain-textsplitters/package.json b/libs/langchain-textsplitters/package.json index a610b2ac5823..65869485c723 100644 --- a/libs/langchain-textsplitters/package.json +++ b/libs/langchain-textsplitters/package.json @@ -44,7 +44,7 @@ }, "devDependencies": { "@jest/globals": "^29.5.0", - "@langchain/scripts": "^0.0.22", + "@langchain/scripts": "^0.0.23", "@swc/core": "^1.3.90", "@swc/jest": "^0.2.29", "@tsconfig/recommended": "^1.0.3", diff --git a/libs/langchain-weaviate/package.json b/libs/langchain-weaviate/package.json index 7ad79c6a7984..521f99b02a31 100644 --- a/libs/langchain-weaviate/package.json +++ b/libs/langchain-weaviate/package.json @@ -47,7 +47,7 @@ "devDependencies": { "@jest/globals": "^29.5.0", "@langchain/openai": "workspace:^", - "@langchain/scripts": "^0.0.22", + "@langchain/scripts": "^0.0.23", "@swc/core": "^1.3.90", "@swc/jest": "^0.2.29", "@tsconfig/recommended": "^1.0.3", diff --git a/libs/langchain-yandex/package.json b/libs/langchain-yandex/package.json index b194e416265d..21e3cf3eb257 100644 --- a/libs/langchain-yandex/package.json +++ b/libs/langchain-yandex/package.json @@ -44,7 +44,7 @@ }, "devDependencies": { "@jest/globals": "^29.5.0", - "@langchain/scripts": "^0.0.22", + "@langchain/scripts": "^0.0.23", "@swc/core": "^1.3.90", "@swc/jest": "^0.2.29", "@tsconfig/recommended": "^1.0.3", diff --git a/yarn.lock b/yarn.lock index 9b54943d05c5..b883767ca2e9 100644 --- a/yarn.lock +++ b/yarn.lock @@ -10854,7 +10854,7 @@ __metadata: "@anthropic-ai/sdk": ^0.25.2 "@jest/globals": ^29.5.0 "@langchain/core": ">=0.2.21 <0.3.0" - "@langchain/scripts": ^0.0.22 + "@langchain/scripts": ^0.0.23 "@langchain/standard-tests": 0.0.0 "@swc/core": ^1.3.90 "@swc/jest": ^0.2.29 @@ -10904,7 +10904,7 @@ __metadata: "@aws-sdk/types": ^3.609.0 "@jest/globals": ^29.5.0 "@langchain/core": ">=0.2.21 <0.3.0" - "@langchain/scripts": ^0.0.22 + "@langchain/scripts": ^0.0.23 "@langchain/standard-tests": 0.0.0 "@smithy/types": ^3.2.0 "@swc/core": ^1.3.90 @@ -10973,7 +10973,7 @@ __metadata: "@azure/identity": ^4.2.1 "@jest/globals": ^29.5.0 "@langchain/core": ~0.2 - "@langchain/scripts": ^0.0.22 + "@langchain/scripts": ^0.0.23 "@swc/core": ^1.3.90 "@swc/jest": ^0.2.29 "@tsconfig/recommended": ^1.0.3 @@ -11008,7 +11008,7 @@ __metadata: "@azure/openai": 1.0.0-beta.11 "@jest/globals": ^29.5.0 "@langchain/core": ">0.1.0 <0.3.0" - "@langchain/scripts": ^0.0.22 + "@langchain/scripts": ^0.0.23 "@langchain/standard-tests": 0.0.0 "@swc/core": ^1.3.90 "@swc/jest": ^0.2.29 @@ -11040,7 +11040,7 @@ __metadata: "@jest/globals": ^29.5.0 "@langchain/core": ">0.1.56 <0.3.0" "@langchain/openai": ~0.1.0 - "@langchain/scripts": ^0.0.22 + "@langchain/scripts": ^0.0.23 "@swc/core": ^1.3.90 "@swc/jest": ^0.2.29 "@tsconfig/recommended": ^1.0.3 @@ -11076,7 +11076,7 @@ __metadata: "@jest/globals": ^29.5.0 "@langchain/core": ">0.1.0 <0.3.0" "@langchain/langgraph": ~0.0.31 - "@langchain/scripts": ^0.0.22 + "@langchain/scripts": ^0.0.23 "@langchain/standard-tests": 0.0.0 "@swc/core": ^1.3.90 "@swc/jest": ^0.2.29 @@ -11114,7 +11114,7 @@ __metadata: dependencies: "@jest/globals": ^29.5.0 "@langchain/core": ">=0.2.21 <0.3.0" - "@langchain/scripts": ^0.0.22 + "@langchain/scripts": ^0.0.23 "@langchain/standard-tests": 0.0.0 "@swc/core": ^1.3.90 "@swc/jest": ^0.2.29 @@ -11181,7 +11181,7 @@ __metadata: "@langchain/core": ">=0.2.21 <0.3.0" "@langchain/langgraph": <0.1.0 "@langchain/openai": ">=0.2.0 <0.3.0" - "@langchain/scripts": ^0.0.22 + "@langchain/scripts": ^0.0.23 "@langchain/standard-tests": 0.0.0 "@layerup/layerup-security": ^1.5.12 "@mendable/firecrawl-js": ^0.0.36 @@ -11692,7 +11692,7 @@ __metadata: resolution: "@langchain/core@workspace:langchain-core" dependencies: "@jest/globals": ^29.5.0 - "@langchain/scripts": ^0.0.22 + "@langchain/scripts": ^0.0.23 "@swc/core": ^1.3.90 "@swc/jest": ^0.2.29 "@types/decamelize": ^1.2.0 @@ -11734,7 +11734,7 @@ __metadata: dependencies: "@jest/globals": ^29.5.0 "@langchain/core": ">0.1.0 <0.3.0" - "@langchain/scripts": ^0.0.22 + "@langchain/scripts": ^0.0.23 "@swc/core": ^1.3.90 "@swc/jest": ^0.2.29 "@tsconfig/recommended": ^1.0.3 @@ -11766,7 +11766,7 @@ __metadata: dependencies: "@jest/globals": ^29.5.0 "@langchain/core": ">=0.2.21 <0.3.0" - "@langchain/scripts": ^0.0.22 + "@langchain/scripts": ^0.0.23 "@swc/core": ^1.3.90 "@swc/jest": ^0.2.29 "@tsconfig/recommended": ^1.0.3 @@ -11800,7 +11800,7 @@ __metadata: "@jest/globals": ^29.5.0 "@langchain/core": ">=0.2.21 <0.3.0" "@langchain/google-common": ~0.0.26 - "@langchain/scripts": ^0.0.22 + "@langchain/scripts": ^0.0.23 "@swc/core": ^1.3.90 "@swc/jest": ^0.2.29 "@tsconfig/recommended": ^1.0.3 @@ -11833,7 +11833,7 @@ __metadata: "@google/generative-ai": ^0.7.0 "@jest/globals": ^29.5.0 "@langchain/core": ">=0.2.21 <0.3.0" - "@langchain/scripts": ^0.0.22 + "@langchain/scripts": ^0.0.23 "@langchain/standard-tests": 0.0.0 "@swc/core": ^1.3.90 "@swc/jest": ^0.2.29 @@ -11869,7 +11869,7 @@ __metadata: "@langchain/core": ">=0.2.21 <0.3.0" "@langchain/google-common": ~0.0 "@langchain/google-webauth": ~0.0.26 - "@langchain/scripts": ^0.0.22 + "@langchain/scripts": ^0.0.23 "@langchain/standard-tests": 0.0.0 "@swc/core": ^1.3.90 "@swc/jest": ^0.2.29 @@ -11903,7 +11903,7 @@ __metadata: "@langchain/core": ">=0.2.21 <0.3.0" "@langchain/google-common": ~0.0 "@langchain/google-gauth": ~0.0.26 - "@langchain/scripts": ^0.0.22 + "@langchain/scripts": ^0.0.23 "@langchain/standard-tests": 0.0.0 "@swc/core": ^1.3.90 "@swc/jest": ^0.2.29 @@ -11936,7 +11936,7 @@ __metadata: "@jest/globals": ^29.5.0 "@langchain/core": ">=0.2.21 <0.3.0" "@langchain/google-common": ~0.0.26 - "@langchain/scripts": ^0.0.22 + "@langchain/scripts": ^0.0.23 "@swc/core": ^1.3.90 "@swc/jest": ^0.2.29 "@tsconfig/recommended": ^1.0.3 @@ -11981,7 +11981,7 @@ __metadata: "@jest/globals": ^29.5.0 "@langchain/core": ">=0.2.21 <0.3.0" "@langchain/openai": "workspace:^" - "@langchain/scripts": ^0.0.22 + "@langchain/scripts": ^0.0.23 "@langchain/standard-tests": 0.0.0 "@swc/core": ^1.3.90 "@swc/jest": ^0.2.29 @@ -12077,7 +12077,7 @@ __metadata: dependencies: "@jest/globals": ^29.5.0 "@langchain/core": ">=0.2.21 <0.3.0" - "@langchain/scripts": ^0.0.22 + "@langchain/scripts": ^0.0.23 "@langchain/standard-tests": 0.0.0 "@mistralai/mistralai": ^1.1.0 "@swc/core": ^1.3.90 @@ -12112,7 +12112,7 @@ __metadata: dependencies: "@jest/globals": ^29.5.0 "@langchain/core": ">=0.2.5 <0.3.0" - "@langchain/scripts": ^0.0.22 + "@langchain/scripts": ^0.0.23 "@langchain/standard-tests": 0.0.0 "@mixedbread-ai/sdk": ^2.2.3 "@swc/core": ^1.3.90 @@ -12146,7 +12146,7 @@ __metadata: "@jest/globals": ^29.5.0 "@langchain/core": ">0.1.0 <0.3.0" "@langchain/openai": "workspace:*" - "@langchain/scripts": ^0.0.22 + "@langchain/scripts": ^0.0.23 "@swc/core": ^1.3.90 "@swc/jest": ^0.2.29 "@tsconfig/recommended": ^1.0.3 @@ -12180,7 +12180,7 @@ __metadata: "@jest/globals": ^29.5.0 "@langchain/core": ">0.1.0 <0.3.0" "@langchain/openai": "workspace:^" - "@langchain/scripts": ^0.0.22 + "@langchain/scripts": ^0.0.23 "@nomic-ai/atlas": ^0.8.0 "@swc/core": ^1.3.90 "@swc/jest": ^0.2.29 @@ -12223,7 +12223,7 @@ __metadata: dependencies: "@jest/globals": ^29.5.0 "@langchain/core": ">=0.2.21 <0.3.0" - "@langchain/scripts": ^0.0.22 + "@langchain/scripts": ^0.0.23 "@langchain/standard-tests": 0.0.0 "@swc/core": ^1.3.90 "@swc/jest": ^0.2.29 @@ -12259,7 +12259,7 @@ __metadata: "@azure/identity": ^4.2.1 "@jest/globals": ^29.5.0 "@langchain/core": ">=0.2.26 <0.3.0" - "@langchain/scripts": ^0.0.22 + "@langchain/scripts": ^0.0.23 "@langchain/standard-tests": 0.0.0 "@swc/core": ^1.3.90 "@swc/jest": ^0.2.29 @@ -12306,7 +12306,7 @@ __metadata: "@jest/globals": ^29.5.0 "@langchain/core": ">0.2.0 <0.3.0" "@langchain/openai": "workspace:*" - "@langchain/scripts": ^0.0.22 + "@langchain/scripts": ^0.0.23 "@pinecone-database/pinecone": ^3.0.0 "@swc/core": ^1.3.90 "@swc/jest": ^0.2.29 @@ -12341,7 +12341,7 @@ __metadata: "@faker-js/faker": ^8.4.1 "@jest/globals": ^29.5.0 "@langchain/core": ">0.1.0 <0.3.0" - "@langchain/scripts": ^0.0.22 + "@langchain/scripts": ^0.0.23 "@qdrant/js-client-rest": ^1.9.0 "@swc/core": ^1.3.90 "@swc/jest": ^0.2.29 @@ -12375,7 +12375,7 @@ __metadata: "@faker-js/faker": ^8.4.0 "@jest/globals": ^29.5.0 "@langchain/core": ">0.1.0 <0.3.0" - "@langchain/scripts": ^0.0.22 + "@langchain/scripts": ^0.0.23 "@swc/core": ^1.3.90 "@swc/jest": ^0.2.29 "@tsconfig/recommended": ^1.0.3 @@ -12402,7 +12402,7 @@ __metadata: languageName: unknown linkType: soft -"@langchain/scripts@^0.0.22, @langchain/scripts@workspace:*, @langchain/scripts@workspace:libs/langchain-scripts, @langchain/scripts@~0.0": +"@langchain/scripts@^0.0.23, @langchain/scripts@workspace:*, @langchain/scripts@workspace:libs/langchain-scripts, @langchain/scripts@~0.0": version: 0.0.0-use.local resolution: "@langchain/scripts@workspace:libs/langchain-scripts" dependencies: @@ -12482,7 +12482,7 @@ __metadata: dependencies: "@jest/globals": ^29.5.0 "@langchain/core": ">0.2.0 <0.3.0" - "@langchain/scripts": ^0.0.22 + "@langchain/scripts": ^0.0.23 "@swc/core": ^1.3.90 "@swc/jest": ^0.2.29 "@tsconfig/recommended": ^1.0.3 @@ -12514,7 +12514,7 @@ __metadata: "@jest/globals": ^29.5.0 "@langchain/core": ">0.2.0 <0.3.0" "@langchain/openai": "workspace:^" - "@langchain/scripts": ^0.0.22 + "@langchain/scripts": ^0.0.23 "@swc/core": ^1.3.90 "@swc/jest": ^0.2.29 "@tsconfig/recommended": ^1.0.3 @@ -12548,7 +12548,7 @@ __metadata: dependencies: "@jest/globals": ^29.5.0 "@langchain/core": ">0.1.0 <0.3.0" - "@langchain/scripts": ^0.0.22 + "@langchain/scripts": ^0.0.23 "@swc/core": ^1.3.90 "@swc/jest": ^0.2.29 "@tsconfig/recommended": ^1.0.3 @@ -26693,7 +26693,7 @@ __metadata: "@langchain/pinecone": "workspace:*" "@langchain/qdrant": "workspace:*" "@langchain/redis": "workspace:*" - "@langchain/scripts": ^0.0.22 + "@langchain/scripts": ^0.0.23 "@langchain/textsplitters": "workspace:*" "@langchain/weaviate": "workspace:*" "@langchain/yandex": "workspace:*" @@ -32058,7 +32058,7 @@ __metadata: "@langchain/mistralai": ^0.0.26 "@langchain/ollama": ^0.0.2 "@langchain/openai": ">=0.1.0 <0.3.0" - "@langchain/scripts": ^0.0.22 + "@langchain/scripts": ^0.0.23 "@langchain/textsplitters": ~0.0.0 "@mendable/firecrawl-js": ^0.0.13 "@notionhq/client": ^2.2.10 From 7534d46e33f47d12d59c3c9599bfa9b3612e9a04 Mon Sep 17 00:00:00 2001 From: Brace Sproul Date: Thu, 22 Aug 2024 10:02:23 -0700 Subject: [PATCH 072/344] docs[patch]: LangSmith doc loader nits (#6590) --- docs/core_docs/.gitignore | 46 ++++++++++--------- .../document_loaders/web_loaders/index.mdx | 2 +- .../web_loaders/langsmith.ipynb | 8 +--- 3 files changed, 27 insertions(+), 29 deletions(-) diff --git a/docs/core_docs/.gitignore b/docs/core_docs/.gitignore index c2737d8dfe94..39187a290670 100644 --- a/docs/core_docs/.gitignore +++ b/docs/core_docs/.gitignore @@ -166,6 +166,8 @@ docs/how_to/extraction_long_text.md docs/how_to/extraction_long_text.mdx docs/how_to/extraction_examples.md docs/how_to/extraction_examples.mdx +docs/how_to/example_selectors_langsmith.md +docs/how_to/example_selectors_langsmith.mdx docs/how_to/example_selectors.md docs/how_to/example_selectors.mdx docs/how_to/document_loader_markdown.md @@ -238,22 +240,18 @@ docs/integrations/vectorstores/elasticsearch.md docs/integrations/vectorstores/elasticsearch.mdx docs/integrations/vectorstores/chroma.md docs/integrations/vectorstores/chroma.mdx -docs/integrations/tools/tavily_search.md -docs/integrations/tools/tavily_search.mdx -docs/integrations/tools/exa_search.md -docs/integrations/tools/exa_search.mdx -docs/integrations/tools/duckduckgo_search.md -docs/integrations/tools/duckduckgo_search.mdx docs/integrations/toolkits/vectorstore.md docs/integrations/toolkits/vectorstore.mdx docs/integrations/toolkits/sql.md docs/integrations/toolkits/sql.mdx docs/integrations/toolkits/openapi.md docs/integrations/toolkits/openapi.mdx -docs/integrations/stores/in_memory.md -docs/integrations/stores/in_memory.mdx -docs/integrations/stores/file_system.md -docs/integrations/stores/file_system.mdx +docs/integrations/tools/tavily_search.md +docs/integrations/tools/tavily_search.mdx +docs/integrations/tools/exa_search.md +docs/integrations/tools/exa_search.mdx +docs/integrations/tools/duckduckgo_search.md +docs/integrations/tools/duckduckgo_search.mdx docs/integrations/text_embedding/togetherai.md docs/integrations/text_embedding/togetherai.mdx docs/integrations/text_embedding/openai.md @@ -276,6 +274,10 @@ docs/integrations/text_embedding/bedrock.md docs/integrations/text_embedding/bedrock.mdx docs/integrations/text_embedding/azure_openai.md docs/integrations/text_embedding/azure_openai.mdx +docs/integrations/stores/in_memory.md +docs/integrations/stores/in_memory.mdx +docs/integrations/stores/file_system.md +docs/integrations/stores/file_system.mdx docs/integrations/retrievers/tavily.md docs/integrations/retrievers/tavily.mdx docs/integrations/retrievers/kendra-retriever.md @@ -348,16 +350,6 @@ docs/integrations/retrievers/self_query/hnswlib.md docs/integrations/retrievers/self_query/hnswlib.mdx docs/integrations/retrievers/self_query/chroma.md docs/integrations/retrievers/self_query/chroma.mdx -docs/integrations/document_loaders/web_loaders/web_puppeteer.md -docs/integrations/document_loaders/web_loaders/web_puppeteer.mdx -docs/integrations/document_loaders/web_loaders/web_cheerio.md -docs/integrations/document_loaders/web_loaders/web_cheerio.mdx -docs/integrations/document_loaders/web_loaders/recursive_url_loader.md -docs/integrations/document_loaders/web_loaders/recursive_url_loader.mdx -docs/integrations/document_loaders/web_loaders/pdf.md -docs/integrations/document_loaders/web_loaders/pdf.mdx -docs/integrations/document_loaders/web_loaders/firecrawl.md -docs/integrations/document_loaders/web_loaders/firecrawl.mdx docs/integrations/document_loaders/file_loaders/unstructured.md docs/integrations/document_loaders/file_loaders/unstructured.mdx docs/integrations/document_loaders/file_loaders/text.md @@ -367,4 +359,16 @@ docs/integrations/document_loaders/file_loaders/pdf.mdx docs/integrations/document_loaders/file_loaders/directory.md docs/integrations/document_loaders/file_loaders/directory.mdx docs/integrations/document_loaders/file_loaders/csv.md -docs/integrations/document_loaders/file_loaders/csv.mdx \ No newline at end of file +docs/integrations/document_loaders/file_loaders/csv.mdx +docs/integrations/document_loaders/web_loaders/web_puppeteer.md +docs/integrations/document_loaders/web_loaders/web_puppeteer.mdx +docs/integrations/document_loaders/web_loaders/web_cheerio.md +docs/integrations/document_loaders/web_loaders/web_cheerio.mdx +docs/integrations/document_loaders/web_loaders/recursive_url_loader.md +docs/integrations/document_loaders/web_loaders/recursive_url_loader.mdx +docs/integrations/document_loaders/web_loaders/pdf.md +docs/integrations/document_loaders/web_loaders/pdf.mdx +docs/integrations/document_loaders/web_loaders/langsmith.md +docs/integrations/document_loaders/web_loaders/langsmith.mdx +docs/integrations/document_loaders/web_loaders/firecrawl.md +docs/integrations/document_loaders/web_loaders/firecrawl.mdx \ No newline at end of file diff --git a/docs/core_docs/docs/integrations/document_loaders/web_loaders/index.mdx b/docs/core_docs/docs/integrations/document_loaders/web_loaders/index.mdx index e9978359f156..b319af708100 100644 --- a/docs/core_docs/docs/integrations/document_loaders/web_loaders/index.mdx +++ b/docs/core_docs/docs/integrations/document_loaders/web_loaders/index.mdx @@ -7,7 +7,7 @@ hide_table_of_contents: true These loaders are used to load web resources. They do not involve the local file system. -import { CategoryTable, IndexTable } from "@theme/FeatureTables"; +import { IndexTable } from "@theme/FeatureTables"; :::info If you'd like to write your own document loader, see [this how-to](/docs/how_to/document_loader_custom/). If you'd like to contribute an integration, see [Contributing integrations](/docs/contributing). diff --git a/docs/core_docs/docs/integrations/document_loaders/web_loaders/langsmith.ipynb b/docs/core_docs/docs/integrations/document_loaders/web_loaders/langsmith.ipynb index b7e536113685..8c622f091353 100644 --- a/docs/core_docs/docs/integrations/document_loaders/web_loaders/langsmith.ipynb +++ b/docs/core_docs/docs/integrations/document_loaders/web_loaders/langsmith.ipynb @@ -5,7 +5,7 @@ "metadata": {}, "source": [ "---\n", - "sidebar_label: FireCrawl\n", + "sidebar_label: LangSmith\n", "\n", "---" ] @@ -29,12 +29,6 @@ "| :---: | :---: | :---: | \n", "| LangSmithLoader | ✅ | ❌ | \n", "\n", - "[FireCrawl](https://firecrawl.dev) crawls and convert any website into LLM-ready data. It crawls all accessible sub-pages and give you clean markdown and metadata for each. No sitemap required.\n", - "\n", - "FireCrawl handles complex tasks such as reverse proxies, caching, rate limits, and content blocked by JavaScript. Built by the [mendable.ai](https://mendable.ai) team.\n", - "\n", - "This guide shows how to scrap and crawl entire websites and load them using the `LangSmithLoader` in LangChain.\n", - "\n", "## Setup\n", "\n", "To access the LangSmith document loader you'll need to install `@langchain/core`, create a [LangSmith](https://langsmith.com/) account and get an API key.\n", From 666911c4b1b7aeb93d9754423efcf1c68c163978 Mon Sep 17 00:00:00 2001 From: Jacob Lee Date: Thu, 22 Aug 2024 11:57:26 -0700 Subject: [PATCH 073/344] community[patch]: Relax LangGraph dep (#6596) --- libs/langchain-community/package.json | 2 +- .../tests/checkpointer.int.test.ts | 2 ++ yarn.lock | 20 +++++++++---------- 3 files changed, 13 insertions(+), 11 deletions(-) diff --git a/libs/langchain-community/package.json b/libs/langchain-community/package.json index 697618778bf8..39547db6b2db 100644 --- a/libs/langchain-community/package.json +++ b/libs/langchain-community/package.json @@ -78,7 +78,7 @@ "@gradientai/nodejs-sdk": "^1.2.0", "@huggingface/inference": "^2.6.4", "@jest/globals": "^29.5.0", - "@langchain/langgraph": "<0.1.0", + "@langchain/langgraph": "*", "@langchain/scripts": "^0.0.23", "@langchain/standard-tests": "0.0.0", "@layerup/layerup-security": "^1.5.12", diff --git a/libs/langchain-community/src/langgraph/checkpointers/tests/checkpointer.int.test.ts b/libs/langchain-community/src/langgraph/checkpointers/tests/checkpointer.int.test.ts index 5037115f7e16..59596040080a 100644 --- a/libs/langchain-community/src/langgraph/checkpointers/tests/checkpointer.int.test.ts +++ b/libs/langchain-community/src/langgraph/checkpointers/tests/checkpointer.int.test.ts @@ -20,6 +20,7 @@ const checkpoint1: Checkpoint = { someKey4: 1, }, }, + pending_sends: [], }; const checkpoint2: Checkpoint = { @@ -37,6 +38,7 @@ const checkpoint2: Checkpoint = { someKey4: 2, }, }, + pending_sends: [], }; describe("VercelKVSaver", () => { diff --git a/yarn.lock b/yarn.lock index b883767ca2e9..b3c68d9ab100 100644 --- a/yarn.lock +++ b/yarn.lock @@ -11179,7 +11179,7 @@ __metadata: "@huggingface/inference": ^2.6.4 "@jest/globals": ^29.5.0 "@langchain/core": ">=0.2.21 <0.3.0" - "@langchain/langgraph": <0.1.0 + "@langchain/langgraph": "*" "@langchain/openai": ">=0.2.0 <0.3.0" "@langchain/scripts": ^0.0.23 "@langchain/standard-tests": 0.0.0 @@ -12010,11 +12010,11 @@ __metadata: languageName: unknown linkType: soft -"@langchain/langgraph@npm:0.0.28, @langchain/langgraph@npm:^0.0.28": - version: 0.0.28 - resolution: "@langchain/langgraph@npm:0.0.28" +"@langchain/langgraph@npm:*": + version: 0.0.34 + resolution: "@langchain/langgraph@npm:0.0.34" dependencies: - "@langchain/core": ">=0.2.16 <0.3.0" + "@langchain/core": ">=0.2.20 <0.3.0" uuid: ^10.0.0 zod: ^3.23.8 peerDependencies: @@ -12022,13 +12022,13 @@ __metadata: peerDependenciesMeta: better-sqlite3: optional: true - checksum: 1465791026ccd6eaa13a2f2d03b8fb9f0972a8c23b9da1cfd581074f413ea60ef860de6d704c6a3b49f7425f23d6ba49c23255167ae83ab7d70dc00cc0560ce2 + checksum: efa3c6b4eb40fdeb696531fe63f1f8e2b5d23b7adfc9404103cc9265db4aab6caee7de9ba1e01e14f2765d98a8b536bfbc473795543b0314e84c72b3b349a258 languageName: node linkType: hard -"@langchain/langgraph@npm:<0.1.0": - version: 0.0.29 - resolution: "@langchain/langgraph@npm:0.0.29" +"@langchain/langgraph@npm:0.0.28, @langchain/langgraph@npm:^0.0.28": + version: 0.0.28 + resolution: "@langchain/langgraph@npm:0.0.28" dependencies: "@langchain/core": ">=0.2.16 <0.3.0" uuid: ^10.0.0 @@ -12038,7 +12038,7 @@ __metadata: peerDependenciesMeta: better-sqlite3: optional: true - checksum: 5a5ed8106efb7b623c26537d7dcb3762b86fdeb2004ebd3dbbe79c6036da318a2bebe904730a7d5376b78d0e03e523e31070824899716d1fa583bed1d05c14e4 + checksum: 1465791026ccd6eaa13a2f2d03b8fb9f0972a8c23b9da1cfd581074f413ea60ef860de6d704c6a3b49f7425f23d6ba49c23255167ae83ab7d70dc00cc0560ce2 languageName: node linkType: hard From a0fbf45c4bb6b17d515ec78cdd4b9f331eb2bd8c Mon Sep 17 00:00:00 2001 From: Brace Sproul Date: Thu, 22 Aug 2024 12:59:41 -0700 Subject: [PATCH 074/344] scripts[minor]infra[patch]: Remove deprecated build script (#6595) --- langchain-core/package.json | 12 +- langchain/package.json | 12 +- .../template/package.json | 11 +- libs/langchain-anthropic/package.json | 2 +- libs/langchain-aws/package.json | 11 +- libs/langchain-azure-cosmosdb/package.json | 14 +- .../package.json | 14 +- libs/langchain-azure-openai/package.json | 2 +- libs/langchain-baidu-qianfan/package.json | 2 +- libs/langchain-cloudflare/package.json | 2 +- libs/langchain-cohere/package.json | 2 +- libs/langchain-community/package.json | 2 +- libs/langchain-exa/package.json | 12 +- libs/langchain-google-common/package.json | 20 +- libs/langchain-google-gauth/package.json | 2 +- libs/langchain-google-genai/package.json | 2 +- .../package.json | 12 +- libs/langchain-google-vertexai/package.json | 15 +- libs/langchain-google-webauth/package.json | 20 +- libs/langchain-groq/package.json | 2 +- libs/langchain-mistralai/package.json | 2 +- libs/langchain-mixedbread-ai/package.json | 2 +- libs/langchain-mongodb/package.json | 12 +- libs/langchain-nomic/package.json | 12 +- libs/langchain-ollama/package.json | 7 +- libs/langchain-openai/package.json | 2 +- libs/langchain-pinecone/package.json | 11 +- libs/langchain-qdrant/package.json | 11 +- libs/langchain-redis/package.json | 11 +- libs/langchain-scripts/bin/build.js | 912 +----------------- libs/langchain-scripts/bin/build_v2.js | 1 - libs/langchain-scripts/langchain.config.js | 2 +- libs/langchain-scripts/package.json | 7 +- libs/langchain-scripts/src/build.ts | 121 --- .../src/{build_v2.ts => build/index.ts} | 81 +- .../src/{ => build}/utils.ts | 2 +- .../src/check-tree-shaking.ts | 122 --- .../src/create-entrypoints.ts | 385 -------- .../langchain-scripts/src/identify-secrets.ts | 100 -- .../langchain-scripts/src/move-cjs-to-dist.ts | 55 -- .../src/tests/config_file.test.ts | 2 +- libs/langchain-scripts/tsconfig.build.json | 3 +- libs/langchain-standard-tests/package.json | 2 +- libs/langchain-textsplitters/package.json | 11 +- libs/langchain-weaviate/package.json | 12 +- libs/langchain-yandex/package.json | 12 +- yarn.lock | 3 +- 47 files changed, 119 insertions(+), 1955 deletions(-) delete mode 100755 libs/langchain-scripts/bin/build_v2.js delete mode 100644 libs/langchain-scripts/src/build.ts rename libs/langchain-scripts/src/{build_v2.ts => build/index.ts} (93%) rename libs/langchain-scripts/src/{ => build}/utils.ts (98%) delete mode 100644 libs/langchain-scripts/src/check-tree-shaking.ts delete mode 100644 libs/langchain-scripts/src/create-entrypoints.ts delete mode 100644 libs/langchain-scripts/src/identify-secrets.ts delete mode 100644 libs/langchain-scripts/src/move-cjs-to-dist.ts diff --git a/langchain-core/package.json b/langchain-core/package.json index 613cb1522b01..1307d14027b3 100644 --- a/langchain-core/package.json +++ b/langchain-core/package.json @@ -15,13 +15,8 @@ "homepage": "https://github.com/langchain-ai/langchainjs/tree/main/langchain-core/", "scripts": { "build": "yarn turbo:command build:internal --filter=@langchain/core", - "build:internal": "yarn lc_build_v2 --create-entrypoints --pre --tree-shaking", + "build:internal": "yarn lc_build --create-entrypoints --pre --tree-shaking", "clean": "rm -rf .turbo dist/", - "build:deps": "yarn turbo build", - "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 && yarn move-cjs-to-dist && rimraf dist-cjs", - "build:watch": "yarn create-entrypoints && tsc --outDir dist/ --watch", - "build:scripts": "yarn create-entrypoints && yarn check-tree-shaking", "lint:eslint": "NODE_OPTIONS=--max-old-space-size=4096 eslint --cache --ext .ts,.js src/", "lint:dpdm": "dpdm --exit-code circular:1 --no-warning --no-tree src/*.ts src/**/*.ts", "lint": "yarn lint:eslint && yarn lint:dpdm", @@ -33,10 +28,7 @@ "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", "format": "prettier --config .prettierrc --write \"src\"", - "format:check": "prettier --config .prettierrc --check \"src\"", - "move-cjs-to-dist": "yarn lc-build --config ./langchain.config.js --move-cjs-dist", - "create-entrypoints": "yarn lc-build --config ./langchain.config.js --create-entrypoints", - "check-tree-shaking": "yarn lc-build --config ./langchain.config.js --tree-shaking" + "format:check": "prettier --config .prettierrc --check \"src\"" }, "author": "LangChain", "license": "MIT", diff --git a/langchain/package.json b/langchain/package.json index fe3c6ee0b599..18aa16d1c363 100644 --- a/langchain/package.json +++ b/langchain/package.json @@ -574,12 +574,7 @@ "homepage": "https://github.com/langchain-ai/langchainjs/tree/main/langchain/", "scripts": { "build": "yarn turbo:command build:internal --filter=langchain", - "build:internal": "yarn lc_build_v2 --create-entrypoints --pre --tree-shaking --gen-maps", - "build:deps": "yarn run turbo:command build --filter=@langchain/openai --filter=@langchain/textsplitters --filter=@langchain/cohere --concurrency=1", - "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 && yarn move-cjs-to-dist && rimraf dist-cjs", - "build:watch": "yarn create-entrypoints && tsc --outDir dist/ --watch", - "build:scripts": "yarn create-entrypoints && yarn check-tree-shaking", + "build:internal": "yarn lc_build --create-entrypoints --pre --tree-shaking --gen-maps", "lint:eslint": "NODE_OPTIONS=--max-old-space-size=4096 eslint --cache --ext .ts,.js src/", "lint:dpdm": "dpdm --exit-code circular:1 --no-warning --no-tree src/*.ts src/**/*.ts", "lint": "yarn lint:eslint && yarn lint:dpdm", @@ -593,10 +588,7 @@ "test:integration": "yarn run build:deps && NODE_OPTIONS=--experimental-vm-modules jest --testPathPattern=\\.int\\.test.ts --testTimeout 100000 --maxWorkers=50%", "test:single": "yarn run build:deps && NODE_OPTIONS=--experimental-vm-modules yarn run jest --config jest.config.cjs --testTimeout 100000", "format": "prettier --config .prettierrc --write \"src\"", - "format:check": "prettier --config .prettierrc --check \"src\"", - "move-cjs-to-dist": "yarn lc-build --config ./langchain.config.js --move-cjs-dist", - "create-entrypoints": "yarn lc-build --config ./langchain.config.js --create-entrypoints", - "check-tree-shaking": "yarn lc-build --config ./langchain.config.js --tree-shaking" + "format:check": "prettier --config .prettierrc --check \"src\"" }, "author": "LangChain", "license": "MIT", diff --git a/libs/create-langchain-integration/template/package.json b/libs/create-langchain-integration/template/package.json index 4b3cb068bf24..dea7417b214a 100644 --- a/libs/create-langchain-integration/template/package.json +++ b/libs/create-langchain-integration/template/package.json @@ -15,11 +15,7 @@ "homepage": "https://github.com/langchain-ai/langchainjs/tree/main/libs/langchain-INTEGRATION_NAME/", "scripts": { "build": "yarn turbo:command build:internal --filter=@langchain/INTEGRATION_NAME", - "build:internal": "yarn lc_build_v2 --create-entrypoints --pre --tree-shaking", - "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 && yarn move-cjs-to-dist && rm -rf dist-cjs", - "build:watch": "yarn create-entrypoints && tsc --outDir dist/ --watch", - "build:scripts": "yarn create-entrypoints && yarn check-tree-shaking", + "build:internal": "yarn lc_build --create-entrypoints --pre --tree-shaking", "lint:eslint": "NODE_OPTIONS=--max-old-space-size=4096 eslint --cache --ext .ts,.js src/", "lint:dpdm": "dpdm --exit-code circular:1 --no-warning --no-tree src/*.ts src/**/*.ts", "lint": "yarn lint:eslint && yarn lint:dpdm", @@ -31,10 +27,7 @@ "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 --config .prettierrc --write \"src\"", - "format:check": "prettier --config .prettierrc --check \"src\"", - "move-cjs-to-dist": "yarn lc-build --config ./langchain.config.js --move-cjs-dist", - "create-entrypoints": "yarn lc-build --config ./langchain.config.js --create-entrypoints", - "check-tree-shaking": "yarn lc-build --config ./langchain.config.js --tree-shaking" + "format:check": "prettier --config .prettierrc --check \"src\"" }, "author": "LangChain", "license": "MIT", diff --git a/libs/langchain-anthropic/package.json b/libs/langchain-anthropic/package.json index f4ef0778ee65..550bb654c321 100644 --- a/libs/langchain-anthropic/package.json +++ b/libs/langchain-anthropic/package.json @@ -15,7 +15,7 @@ "homepage": "https://github.com/langchain-ai/langchainjs/tree/main/libs/langchain-anthropic/", "scripts": { "build": "yarn turbo:command build:internal --filter=@langchain/anthropic", - "build:internal": "yarn lc_build_v2 --create-entrypoints --pre --tree-shaking --gen-maps", + "build:internal": "yarn lc_build --create-entrypoints --pre --tree-shaking --gen-maps", "lint:eslint": "NODE_OPTIONS=--max-old-space-size=4096 eslint --cache --ext .ts,.js src/", "lint:dpdm": "dpdm --exit-code circular:1 --no-warning --no-tree src/*.ts src/**/*.ts", "lint": "yarn lint:eslint && yarn lint:dpdm", diff --git a/libs/langchain-aws/package.json b/libs/langchain-aws/package.json index d9a4990e1146..a4a18e716fa9 100644 --- a/libs/langchain-aws/package.json +++ b/libs/langchain-aws/package.json @@ -15,11 +15,7 @@ "homepage": "https://github.com/langchain-ai/langchainjs/tree/main/libs/langchain-aws/", "scripts": { "build": "yarn turbo:command build:internal --filter=@langchain/aws", - "build:internal": "yarn lc_build_v2 --create-entrypoints --pre --tree-shaking", - "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 && yarn move-cjs-to-dist && rm -rf dist-cjs", - "build:watch": "yarn create-entrypoints && tsc --outDir dist/ --watch", - "build:scripts": "yarn create-entrypoints && yarn check-tree-shaking", + "build:internal": "yarn lc_build --create-entrypoints --pre --tree-shaking", "lint:eslint": "NODE_OPTIONS=--max-old-space-size=4096 eslint --cache --ext .ts,.js src/", "lint:dpdm": "dpdm --exit-code circular:1 --no-warning --no-tree src/*.ts src/**/*.ts", "lint": "yarn lint:eslint && yarn lint:dpdm", @@ -31,10 +27,7 @@ "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 --config .prettierrc --write \"src\"", - "format:check": "prettier --config .prettierrc --check \"src\"", - "move-cjs-to-dist": "yarn lc-build --config ./langchain.config.js --move-cjs-dist", - "create-entrypoints": "yarn lc-build --config ./langchain.config.js --create-entrypoints", - "check-tree-shaking": "yarn lc-build --config ./langchain.config.js --tree-shaking" + "format:check": "prettier --config .prettierrc --check \"src\"" }, "author": "LangChain", "license": "MIT", diff --git a/libs/langchain-azure-cosmosdb/package.json b/libs/langchain-azure-cosmosdb/package.json index f825d546f520..6a8a02adebb1 100644 --- a/libs/langchain-azure-cosmosdb/package.json +++ b/libs/langchain-azure-cosmosdb/package.json @@ -14,26 +14,20 @@ }, "homepage": "https://github.com/langchain-ai/langchainjs/tree/main/libs/langchain-azure-cosmosdb/", "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 && yarn move-cjs-to-dist && rm -rf dist-cjs", - "build:watch": "yarn create-entrypoints && tsc --outDir dist/ --watch", - "build:scripts": "yarn create-entrypoints && yarn check-tree-shaking", + "build": "yarn turbo:command build:internal --filter=@langchain/azure-cosmosdb", + "build:internal": "yarn lc_build --create-entrypoints --pre --tree-shaking", "lint:eslint": "NODE_OPTIONS=--max-old-space-size=4096 eslint --cache --ext .ts,.js src/", "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": "rm -rf dist/ && NODE_OPTIONS=--max-old-space-size=4096 yarn lc-build --config ./langchain.config.js --create-entrypoints --pre", + "clean": "rm -rf dist/ .turbo", "prepack": "yarn build", "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 --config .prettierrc --write \"src\"", - "format:check": "prettier --config .prettierrc --check \"src\"", - "move-cjs-to-dist": "yarn lc-build --config ./langchain.config.js --move-cjs-dist", - "create-entrypoints": "yarn lc-build --config ./langchain.config.js --create-entrypoints", - "check-tree-shaking": "yarn lc-build --config ./langchain.config.js --tree-shaking" + "format:check": "prettier --config .prettierrc --check \"src\"" }, "author": "LangChain", "license": "MIT", diff --git a/libs/langchain-azure-dynamic-sessions/package.json b/libs/langchain-azure-dynamic-sessions/package.json index c726d00e93cf..2c8e0ef7e80c 100644 --- a/libs/langchain-azure-dynamic-sessions/package.json +++ b/libs/langchain-azure-dynamic-sessions/package.json @@ -14,26 +14,20 @@ }, "homepage": "https://github.com/langchain-ai/langchainjs/tree/main/libs/langchain-INTEGRATION_NAME/", "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 && yarn move-cjs-to-dist && rm -rf dist-cjs", - "build:watch": "yarn create-entrypoints && tsc --outDir dist/ --watch", - "build:scripts": "yarn create-entrypoints && yarn check-tree-shaking", + "build": "yarn turbo:command build:internal --filter=@langchain/azure-dynamic-sessions", + "build:internal": "yarn lc_build --create-entrypoints --pre --tree-shaking", "lint:eslint": "NODE_OPTIONS=--max-old-space-size=4096 eslint --cache --ext .ts,.js src/", "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": "rm -rf dist/ && NODE_OPTIONS=--max-old-space-size=4096 yarn lc-build --config ./langchain.config.js --create-entrypoints --pre", + "clean": "rm -rf .turbo dist/", "prepack": "yarn build", "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 --config .prettierrc --write \"src\"", - "format:check": "prettier --config .prettierrc --check \"src\"", - "move-cjs-to-dist": "yarn lc-build --config ./langchain.config.js --move-cjs-dist", - "create-entrypoints": "yarn lc-build --config ./langchain.config.js --create-entrypoints", - "check-tree-shaking": "yarn lc-build --config ./langchain.config.js --tree-shaking" + "format:check": "prettier --config .prettierrc --check \"src\"" }, "author": "LangChain", "license": "MIT", diff --git a/libs/langchain-azure-openai/package.json b/libs/langchain-azure-openai/package.json index 26631a07a7d8..7d342c60ec33 100644 --- a/libs/langchain-azure-openai/package.json +++ b/libs/langchain-azure-openai/package.json @@ -14,7 +14,7 @@ }, "scripts": { "build": "yarn turbo:command build:internal --filter=@langchain/azure-openai", - "build:internal": "yarn lc_build_v2 --create-entrypoints --pre --tree-shaking", + "build:internal": "yarn lc_build --create-entrypoints --pre --tree-shaking", "lint:eslint": "NODE_OPTIONS=--max-old-space-size=4096 eslint --cache --ext .ts,.js src/", "lint:dpdm": "dpdm --exit-code circular:1 --no-warning --no-tree src/*.ts src/**/*.ts", "lint": "yarn lint:eslint && yarn lint:dpdm", diff --git a/libs/langchain-baidu-qianfan/package.json b/libs/langchain-baidu-qianfan/package.json index 50d9b678f926..84cf871e7e09 100644 --- a/libs/langchain-baidu-qianfan/package.json +++ b/libs/langchain-baidu-qianfan/package.json @@ -15,7 +15,7 @@ "homepage": "https://github.com/langchain-ai/langchainjs/tree/main/libs/langchain-baidu-qianfan/", "scripts": { "build": "yarn turbo:command build:internal --filter=@langchain/baidu-qianfan", - "build:internal": "yarn lc_build_v2 --create-entrypoints --pre --tree-shaking", + "build:internal": "yarn lc_build --create-entrypoints --pre --tree-shaking", "lint:eslint": "NODE_OPTIONS=--max-old-space-size=4096 eslint --cache --ext .ts,.js src/", "lint:dpdm": "dpdm --exit-code circular:1 --no-warning --no-tree src/*.ts src/**/*.ts", "lint": "yarn lint:eslint && yarn lint:dpdm", diff --git a/libs/langchain-cloudflare/package.json b/libs/langchain-cloudflare/package.json index 45bd5752740e..171bd43a0fdc 100644 --- a/libs/langchain-cloudflare/package.json +++ b/libs/langchain-cloudflare/package.json @@ -15,7 +15,7 @@ "homepage": "https://github.com/langchain-ai/langchainjs/tree/main/libs/langchain-cloudflare/", "scripts": { "build": "yarn turbo:command build:internal --filter=@langchain/cloudflare", - "build:internal": "yarn lc_build_v2 --create-entrypoints --pre --tree-shaking", + "build:internal": "yarn lc_build --create-entrypoints --pre --tree-shaking", "lint:eslint": "NODE_OPTIONS=--max-old-space-size=4096 eslint --cache --ext .ts,.js src/", "lint:dpdm": "dpdm --exit-code circular:1 --no-warning --no-tree src/*.ts src/**/*.ts", "lint": "yarn lint:eslint && yarn lint:dpdm", diff --git a/libs/langchain-cohere/package.json b/libs/langchain-cohere/package.json index 634afa79e05a..eeb1035c0fde 100644 --- a/libs/langchain-cohere/package.json +++ b/libs/langchain-cohere/package.json @@ -15,7 +15,7 @@ "homepage": "https://github.com/langchain-ai/langchainjs/tree/main/libs/langchain-cohere/", "scripts": { "build": "yarn turbo:command build:internal --filter=@langchain/cohere", - "build:internal": "yarn lc_build_v2 --create-entrypoints --pre --tree-shaking", + "build:internal": "yarn lc_build --create-entrypoints --pre --tree-shaking", "lint:eslint": "NODE_OPTIONS=--max-old-space-size=4096 eslint --cache --ext .ts,.js src/", "lint:dpdm": "dpdm --exit-code circular:1 --no-warning --no-tree src/*.ts src/**/*.ts", "lint": "yarn lint:eslint && yarn lint:dpdm", diff --git a/libs/langchain-community/package.json b/libs/langchain-community/package.json index 39547db6b2db..57fa2d83e33f 100644 --- a/libs/langchain-community/package.json +++ b/libs/langchain-community/package.json @@ -15,7 +15,7 @@ "homepage": "https://github.com/langchain-ai/langchainjs/tree/main/libs/langchain-community/", "scripts": { "build": "yarn turbo:command build:internal --filter=@langchain/community", - "build:internal": "yarn lc_build_v2 --create-entrypoints --pre --tree-shaking --gen-maps", + "build:internal": "yarn lc_build --create-entrypoints --pre --tree-shaking --gen-maps", "lint:eslint": "NODE_OPTIONS=--max-old-space-size=4096 eslint --cache --ext .ts,.js src/", "lint:dpdm": "dpdm --exit-code circular:1 --no-warning --no-tree src/*.ts src/**/*.ts", "lint": "yarn lint:eslint && yarn lint:dpdm", diff --git a/libs/langchain-exa/package.json b/libs/langchain-exa/package.json index 6f112e3223a1..20b313377c36 100644 --- a/libs/langchain-exa/package.json +++ b/libs/langchain-exa/package.json @@ -15,12 +15,7 @@ "homepage": "https://github.com/langchain-ai/langchainjs/tree/main/libs/langchain-exa/", "scripts": { "build": "yarn turbo:command build:internal --filter=@langchain/exa", - "build:internal": "yarn lc_build_v2 --create-entrypoints --pre --tree-shaking", - "build:deps": "yarn run turbo:command build --filter=@langchain/core", - "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 && yarn move-cjs-to-dist && rm -rf dist-cjs", - "build:watch": "yarn create-entrypoints && tsc --outDir dist/ --watch", - "build:scripts": "yarn create-entrypoints && yarn check-tree-shaking", + "build:internal": "yarn lc_build --create-entrypoints --pre --tree-shaking", "lint:eslint": "NODE_OPTIONS=--max-old-space-size=4096 eslint --cache --ext .ts,.js src/", "lint:dpdm": "dpdm --exit-code circular:1 --no-warning --no-tree src/*.ts src/**/*.ts", "lint": "yarn lint:eslint && yarn lint:dpdm", @@ -32,10 +27,7 @@ "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 --config .prettierrc --write \"src\"", - "format:check": "prettier --config .prettierrc --check \"src\"", - "move-cjs-to-dist": "yarn lc-build --config ./langchain.config.js --move-cjs-dist", - "create-entrypoints": "yarn lc-build --config ./langchain.config.js --create-entrypoints", - "check-tree-shaking": "yarn lc-build --config ./langchain.config.js --tree-shaking" + "format:check": "prettier --config .prettierrc --check \"src\"" }, "author": "LangChain", "license": "MIT", diff --git a/libs/langchain-google-common/package.json b/libs/langchain-google-common/package.json index 0d96e938a1db..634c32d400e7 100644 --- a/libs/langchain-google-common/package.json +++ b/libs/langchain-google-common/package.json @@ -15,27 +15,19 @@ "homepage": "https://github.com/langchain-ai/langchainjs/tree/main/libs/langchain-google-common/", "scripts": { "build": "yarn turbo:command build:internal --filter=@langchain/google-common", - "build:internal": "yarn lc_build_v2 --create-entrypoints --pre --tree-shaking", - "build:deps": "yarn run turbo:command build --filter=@langchain/core", - "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 && yarn move-cjs-to-dist && rm -rf dist-cjs", - "build:watch": "yarn create-entrypoints && tsc --outDir dist/ --watch", - "build:scripts": "yarn create-entrypoints && yarn check-tree-shaking", + "build:internal": "yarn lc_build --create-entrypoints --pre --tree-shaking", "lint:eslint": "NODE_OPTIONS=--max-old-space-size=4096 eslint --cache --ext .ts,.js src/", "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": "rm -rf .turbo dist/", "prepack": "yarn build", - "test": "yarn run build:deps && NODE_OPTIONS=--experimental-vm-modules jest --testPathIgnorePatterns=\\.int\\.test.ts --testTimeout 30000 --maxWorkers=50%", - "test:watch": "yarn run build:deps && NODE_OPTIONS=--experimental-vm-modules jest --watch --testPathIgnorePatterns=\\.int\\.test.ts", - "test:single": "yarn run build:deps && NODE_OPTIONS=--experimental-vm-modules yarn run jest --config jest.config.cjs --testTimeout 100000", - "test:integration": "NODE_OPTIONS=--experimental-vm-modules jest --testPathPattern=\\.int\\.test.ts --testTimeout 100000 --maxWorkers=50%", + "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 --config .prettierrc --write \"src\"", - "format:check": "prettier --config .prettierrc --check \"src\"", - "move-cjs-to-dist": "yarn lc-build --config ./langchain.config.js --move-cjs-dist", - "create-entrypoints": "yarn lc-build --config ./langchain.config.js --create-entrypoints", - "check-tree-shaking": "yarn lc-build --config ./langchain.config.js --tree-shaking" + "format:check": "prettier --config .prettierrc --check \"src\"" }, "author": "LangChain", "license": "MIT", diff --git a/libs/langchain-google-gauth/package.json b/libs/langchain-google-gauth/package.json index 0fea7767dba0..f6821c67f17c 100644 --- a/libs/langchain-google-gauth/package.json +++ b/libs/langchain-google-gauth/package.json @@ -15,7 +15,7 @@ "homepage": "https://github.com/langchain-ai/langchainjs/tree/main/libs/langchain-google-gauth/", "scripts": { "build": "yarn turbo:command build:internal --filter=@langchain/google-gauth", - "build:internal": "yarn lc_build_v2 --create-entrypoints --pre --tree-shaking", + "build:internal": "yarn lc_build --create-entrypoints --pre --tree-shaking", "lint:eslint": "NODE_OPTIONS=--max-old-space-size=4096 eslint --cache --ext .ts,.js src/", "lint:dpdm": "dpdm --exit-code circular:1 --no-warning --no-tree src/*.ts src/**/*.ts", "lint": "yarn lint:eslint && yarn lint:dpdm", diff --git a/libs/langchain-google-genai/package.json b/libs/langchain-google-genai/package.json index dd54061733df..d6792fd43c07 100644 --- a/libs/langchain-google-genai/package.json +++ b/libs/langchain-google-genai/package.json @@ -15,7 +15,7 @@ "homepage": "https://github.com/langchain-ai/langchainjs/tree/main/libs/langchain-google-genai/", "scripts": { "build": "yarn turbo:command build:internal --filter=@langchain/google-genai", - "build:internal": "yarn lc_build_v2 --create-entrypoints --pre --tree-shaking", + "build:internal": "yarn lc_build --create-entrypoints --pre --tree-shaking", "lint:eslint": "NODE_OPTIONS=--max-old-space-size=4096 eslint --cache --ext .ts,.js src/", "lint:dpdm": "dpdm --exit-code circular:1 --no-warning --no-tree src/*.ts src/**/*.ts", "lint": "yarn lint:eslint && yarn lint:dpdm", diff --git a/libs/langchain-google-vertexai-web/package.json b/libs/langchain-google-vertexai-web/package.json index 13fec9df09b7..26341f930fa3 100644 --- a/libs/langchain-google-vertexai-web/package.json +++ b/libs/langchain-google-vertexai-web/package.json @@ -15,12 +15,7 @@ "homepage": "https://github.com/langchain-ai/langchainjs/tree/main/libs/langchain-google-vertexai-web/", "scripts": { "build": "yarn turbo:command build:internal --filter=@langchain/google-vertexai-web", - "build:internal": "yarn lc_build_v2 --create-entrypoints --pre --tree-shaking", - "build:deps": "yarn run turbo:command build --filter=@langchain/google-gauth", - "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 && yarn move-cjs-to-dist && rm -rf dist-cjs", - "build:watch": "yarn create-entrypoints && tsc --outDir dist/ --watch", - "build:scripts": "yarn create-entrypoints && yarn check-tree-shaking", + "build:internal": "yarn lc_build --create-entrypoints --pre --tree-shaking", "lint:eslint": "NODE_OPTIONS=--max-old-space-size=4096 eslint --cache --ext .ts,.js src/", "lint:dpdm": "dpdm --exit-code circular:1 --no-warning --no-tree src/*.ts src/**/*.ts", "lint": "yarn lint:eslint && yarn lint:dpdm", @@ -32,10 +27,7 @@ "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 --config .prettierrc --write \"src\"", - "format:check": "prettier --config .prettierrc --check \"src\"", - "move-cjs-to-dist": "yarn lc-build --config ./langchain.config.js --move-cjs-dist", - "create-entrypoints": "yarn lc-build --config ./langchain.config.js --create-entrypoints", - "check-tree-shaking": "yarn lc-build --config ./langchain.config.js --tree-shaking" + "format:check": "prettier --config .prettierrc --check \"src\"" }, "author": "LangChain", "license": "MIT", diff --git a/libs/langchain-google-vertexai/package.json b/libs/langchain-google-vertexai/package.json index 5a0faf98f3b7..176d427f54fe 100644 --- a/libs/langchain-google-vertexai/package.json +++ b/libs/langchain-google-vertexai/package.json @@ -15,12 +15,7 @@ "homepage": "https://github.com/langchain-ai/langchainjs/tree/main/libs/langchain-google-vertexai/", "scripts": { "build": "yarn turbo:command build:internal --filter=@langchain/google-vertexai", - "build:internal": "yarn lc_build_v2 --create-entrypoints --pre --tree-shaking", - "build:deps": "yarn run turbo:command build --filter=@langchain/google-gauth", - "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 && yarn move-cjs-to-dist && rm -rf dist-cjs", - "build:watch": "yarn create-entrypoints && tsc --outDir dist/ --watch", - "build:scripts": "yarn create-entrypoints && yarn check-tree-shaking", + "build:internal": "yarn lc_build --create-entrypoints --pre --tree-shaking", "lint:eslint": "NODE_OPTIONS=--max-old-space-size=4096 eslint --cache --ext .ts,.js src/", "lint:dpdm": "dpdm --exit-code circular:1 --no-warning --no-tree src/*.ts src/**/*.ts", "lint": "yarn lint:eslint && yarn lint:dpdm", @@ -31,14 +26,8 @@ "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%", - "test:standard:unit": "NODE_OPTIONS=--experimental-vm-modules jest --testPathPattern=\\.standard\\.test.ts --testTimeout 100000 --maxWorkers=50%", - "test:standard:int": "NODE_OPTIONS=--experimental-vm-modules jest --testPathPattern=\\.standard\\.int\\.test.ts --testTimeout 100000 --maxWorkers=50%", - "test:standard": "yarn test:standard:unit && yarn test:standard:int", "format": "prettier --config .prettierrc --write \"src\"", - "format:check": "prettier --config .prettierrc --check \"src\"", - "move-cjs-to-dist": "yarn lc-build --config ./langchain.config.js --move-cjs-dist", - "create-entrypoints": "yarn lc-build --config ./langchain.config.js --create-entrypoints", - "check-tree-shaking": "yarn lc-build --config ./langchain.config.js --tree-shaking" + "format:check": "prettier --config .prettierrc --check \"src\"" }, "author": "LangChain", "license": "MIT", diff --git a/libs/langchain-google-webauth/package.json b/libs/langchain-google-webauth/package.json index e427d62c0ddd..efdd9a744a48 100644 --- a/libs/langchain-google-webauth/package.json +++ b/libs/langchain-google-webauth/package.json @@ -15,27 +15,19 @@ "homepage": "https://github.com/langchain-ai/langchainjs/tree/main/libs/langchain-google-webauth/", "scripts": { "build": "yarn turbo:command build:internal --filter=@langchain/google-webauth", - "build:internal": "yarn lc_build_v2 --create-entrypoints --pre --tree-shaking", - "build:deps": "yarn run turbo:command build --filter=@langchain/google-common", - "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 && yarn move-cjs-to-dist && rm -rf dist-cjs", - "build:watch": "yarn create-entrypoints && tsc --outDir dist/ --watch", - "build:scripts": "yarn create-entrypoints && yarn check-tree-shaking", + "build:internal": "yarn lc_build --create-entrypoints --pre --tree-shaking", "lint:eslint": "NODE_OPTIONS=--max-old-space-size=4096 eslint --cache --ext .ts,.js src/", "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": "rm -rf .turbo dist/", "prepack": "yarn build", - "test": "yarn run build:deps && NODE_OPTIONS=--experimental-vm-modules jest --testPathIgnorePatterns=\\.int\\.test.ts --testTimeout 30000 --maxWorkers=50%", - "test:watch": "yarn run build:deps && NODE_OPTIONS=--experimental-vm-modules jest --watch --testPathIgnorePatterns=\\.int\\.test.ts", - "test:single": "yarn run build:deps && NODE_OPTIONS=--experimental-vm-modules yarn run jest --config jest.config.cjs --testTimeout 100000", - "test:integration": "NODE_OPTIONS=--experimental-vm-modules jest --testPathPattern=\\.int\\.test.ts --testTimeout 100000 --maxWorkers=50%", + "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 --config .prettierrc --write \"src\"", - "format:check": "prettier --config .prettierrc --check \"src\"", - "move-cjs-to-dist": "yarn lc-build --config ./langchain.config.js --move-cjs-dist", - "create-entrypoints": "yarn lc-build --config ./langchain.config.js --create-entrypoints", - "check-tree-shaking": "yarn lc-build --config ./langchain.config.js --tree-shaking" + "format:check": "prettier --config .prettierrc --check \"src\"" }, "author": "LangChain", "license": "MIT", diff --git a/libs/langchain-groq/package.json b/libs/langchain-groq/package.json index 42c79b7b73fd..616bebcf750b 100644 --- a/libs/langchain-groq/package.json +++ b/libs/langchain-groq/package.json @@ -15,7 +15,7 @@ "homepage": "https://github.com/langchain-ai/langchainjs/tree/main/libs/langchain-groq/", "scripts": { "build": "yarn turbo:command build:internal --filter=@langchain/groq", - "build:internal": "yarn lc_build_v2 --create-entrypoints --pre --tree-shaking", + "build:internal": "yarn lc_build --create-entrypoints --pre --tree-shaking", "lint:eslint": "NODE_OPTIONS=--max-old-space-size=4096 eslint --cache --ext .ts,.js src/", "lint:dpdm": "dpdm --exit-code circular:1 --no-warning --no-tree src/*.ts src/**/*.ts", "lint": "yarn lint:eslint && yarn lint:dpdm", diff --git a/libs/langchain-mistralai/package.json b/libs/langchain-mistralai/package.json index c92f0fa42432..c757c40b9750 100644 --- a/libs/langchain-mistralai/package.json +++ b/libs/langchain-mistralai/package.json @@ -15,7 +15,7 @@ "homepage": "https://github.com/langchain-ai/langchainjs/tree/main/libs/langchain-mistralai/", "scripts": { "build": "yarn turbo:command build:internal --filter=@langchain/mistralai", - "build:internal": "yarn lc_build_v2 --create-entrypoints --pre --tree-shaking", + "build:internal": "yarn lc_build --create-entrypoints --pre --tree-shaking", "lint:eslint": "NODE_OPTIONS=--max-old-space-size=4096 eslint --cache --ext .ts,.js src/", "lint:dpdm": "dpdm --exit-code circular:1 --no-warning --no-tree src/*.ts src/**/*.ts", "lint": "yarn lint:eslint && yarn lint:dpdm", diff --git a/libs/langchain-mixedbread-ai/package.json b/libs/langchain-mixedbread-ai/package.json index 68d207ac8877..4a9015891868 100644 --- a/libs/langchain-mixedbread-ai/package.json +++ b/libs/langchain-mixedbread-ai/package.json @@ -15,7 +15,7 @@ "homepage": "https://github.com/langchain-ai/langchainjs/tree/main/libs/langchain-mixedbread-ai/", "scripts": { "build": "yarn turbo:command build:internal --filter=@langchain/mixedbread-ai", - "build:internal": "yarn lc_build_v2 --create-entrypoints --pre --tree-shaking", + "build:internal": "yarn lc_build --create-entrypoints --pre --tree-shaking", "lint:eslint": "NODE_OPTIONS=--max-old-space-size=4096 eslint --cache --ext .ts,.js src/", "lint:dpdm": "dpdm --exit-code circular:1 --no-warning --no-tree src/*.ts src/**/*.ts", "lint": "yarn lint:eslint && yarn lint:dpdm", diff --git a/libs/langchain-mongodb/package.json b/libs/langchain-mongodb/package.json index cd526c859df5..9b6b1557e330 100644 --- a/libs/langchain-mongodb/package.json +++ b/libs/langchain-mongodb/package.json @@ -15,12 +15,7 @@ "homepage": "https://github.com/langchain-ai/langchainjs/tree/main/libs/langchain-mongodb/", "scripts": { "build": "yarn turbo:command build:internal --filter=@langchain/mongodb", - "build:internal": "yarn lc_build_v2 --create-entrypoints --pre --tree-shaking", - "build:deps": "yarn run turbo:command build --filter=@langchain/openai", - "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 && yarn move-cjs-to-dist && rm -rf dist-cjs", - "build:watch": "yarn create-entrypoints && tsc --outDir dist/ --watch", - "build:scripts": "yarn create-entrypoints && yarn check-tree-shaking", + "build:internal": "yarn lc_build --create-entrypoints --pre --tree-shaking", "lint:eslint": "NODE_OPTIONS=--max-old-space-size=4096 eslint --cache --ext .ts,.js src/", "lint:dpdm": "dpdm --exit-code circular:1 --no-warning --no-tree src/*.ts src/**/*.ts", "lint": "yarn lint:eslint && yarn lint:dpdm", @@ -32,10 +27,7 @@ "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 --config .prettierrc --write \"src\"", - "format:check": "prettier --config .prettierrc --check \"src\"", - "move-cjs-to-dist": "yarn lc-build --config ./langchain.config.js --move-cjs-dist", - "create-entrypoints": "yarn lc-build --config ./langchain.config.js --create-entrypoints", - "check-tree-shaking": "yarn lc-build --config ./langchain.config.js --tree-shaking" + "format:check": "prettier --config .prettierrc --check \"src\"" }, "author": "LangChain", "license": "MIT", diff --git a/libs/langchain-nomic/package.json b/libs/langchain-nomic/package.json index 76daa46b574b..0c676b062876 100644 --- a/libs/langchain-nomic/package.json +++ b/libs/langchain-nomic/package.json @@ -15,12 +15,7 @@ "homepage": "https://github.com/langchain-ai/langchainjs/tree/main/libs/langchain-nomic/", "scripts": { "build": "yarn turbo:command build:internal --filter=@langchain/nomic", - "build:internal": "yarn lc_build_v2 --create-entrypoints --pre --tree-shaking", - "build:deps": "yarn run turbo:command build --filter=@langchain/core", - "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 && yarn move-cjs-to-dist && rm -rf dist-cjs", - "build:watch": "yarn create-entrypoints && tsc --outDir dist/ --watch", - "build:scripts": "yarn create-entrypoints && yarn check-tree-shaking", + "build:internal": "yarn lc_build --create-entrypoints --pre --tree-shaking", "lint:eslint": "NODE_OPTIONS=--max-old-space-size=4096 eslint --cache --ext .ts,.js src/", "lint:dpdm": "dpdm --exit-code circular:1 --no-warning --no-tree src/*.ts src/**/*.ts", "lint": "yarn lint:eslint && yarn lint:dpdm", @@ -32,10 +27,7 @@ "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 --config .prettierrc --write \"src\"", - "format:check": "prettier --config .prettierrc --check \"src\"", - "move-cjs-to-dist": "yarn lc-build --config ./langchain.config.js --move-cjs-dist", - "create-entrypoints": "yarn lc-build --config ./langchain.config.js --create-entrypoints", - "check-tree-shaking": "yarn lc-build --config ./langchain.config.js --tree-shaking" + "format:check": "prettier --config .prettierrc --check \"src\"" }, "author": "LangChain", "license": "MIT", diff --git a/libs/langchain-ollama/package.json b/libs/langchain-ollama/package.json index 88d5c74d7917..c18f33538808 100644 --- a/libs/langchain-ollama/package.json +++ b/libs/langchain-ollama/package.json @@ -15,7 +15,7 @@ "homepage": "https://github.com/langchain-ai/langchainjs/tree/main/libs/langchain-ollama/", "scripts": { "build": "yarn turbo:command build:internal --filter=@langchain/ollama", - "build:internal": "yarn lc_build_v2 --create-entrypoints --pre --tree-shaking", + "build:internal": "yarn lc_build --create-entrypoints --pre --tree-shaking", "lint:eslint": "NODE_OPTIONS=--max-old-space-size=4096 eslint --cache --ext .ts,.js src/", "lint:dpdm": "dpdm --exit-code circular:1 --no-warning --no-tree src/*.ts src/**/*.ts", "lint": "yarn lint:eslint && yarn lint:dpdm", @@ -27,10 +27,7 @@ "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 --config .prettierrc --write \"src\"", - "format:check": "prettier --config .prettierrc --check \"src\"", - "move-cjs-to-dist": "yarn lc-build --config ./langchain.config.js --move-cjs-dist", - "create-entrypoints": "yarn lc-build --config ./langchain.config.js --create-entrypoints", - "check-tree-shaking": "yarn lc-build --config ./langchain.config.js --tree-shaking" + "format:check": "prettier --config .prettierrc --check \"src\"" }, "author": "LangChain", "license": "MIT", diff --git a/libs/langchain-openai/package.json b/libs/langchain-openai/package.json index 4ba163dd8703..831946ffc1fd 100644 --- a/libs/langchain-openai/package.json +++ b/libs/langchain-openai/package.json @@ -15,7 +15,7 @@ "homepage": "https://github.com/langchain-ai/langchainjs/tree/main/libs/langchain-openai/", "scripts": { "build": "yarn turbo:command build:internal --filter=@langchain/openai", - "build:internal": "yarn lc_build_v2 --create-entrypoints --pre --tree-shaking", + "build:internal": "yarn lc_build --create-entrypoints --pre --tree-shaking", "lint:eslint": "NODE_OPTIONS=--max-old-space-size=4096 eslint --cache --ext .ts,.js src/", "lint:dpdm": "dpdm --exit-code circular:1 --no-warning --no-tree src/*.ts src/**/*.ts", "lint": "yarn lint:eslint && yarn lint:dpdm", diff --git a/libs/langchain-pinecone/package.json b/libs/langchain-pinecone/package.json index 702a8ff3d590..eed6620abc5f 100644 --- a/libs/langchain-pinecone/package.json +++ b/libs/langchain-pinecone/package.json @@ -15,11 +15,7 @@ "homepage": "https://github.com/langchain-ai/langchainjs/tree/main/libs/langchain-pinecone/", "scripts": { "build": "yarn turbo:command build:internal --filter=@langchain/pinecone", - "build:internal": "yarn lc_build_v2 --create-entrypoints --pre --tree-shaking", - "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 && yarn move-cjs-to-dist && rm -rf dist-cjs", - "build:watch": "yarn create-entrypoints && tsc --outDir dist/ --watch", - "build:scripts": "yarn create-entrypoints && yarn check-tree-shaking", + "build:internal": "yarn lc_build --create-entrypoints --pre --tree-shaking", "lint:eslint": "NODE_OPTIONS=--max-old-space-size=4096 eslint --cache --ext .ts,.js src/", "lint:dpdm": "dpdm --exit-code circular:1 --no-warning --no-tree src/*.ts src/**/*.ts", "lint": "yarn lint:eslint && yarn lint:dpdm", @@ -31,10 +27,7 @@ "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 --config .prettierrc --write \"src\"", - "format:check": "prettier --config .prettierrc --check \"src\"", - "move-cjs-to-dist": "yarn lc-build --config ./langchain.config.js --move-cjs-dist", - "create-entrypoints": "yarn lc-build --config ./langchain.config.js --create-entrypoints", - "check-tree-shaking": "yarn lc-build --config ./langchain.config.js --tree-shaking" + "format:check": "prettier --config .prettierrc --check \"src\"" }, "author": "Pinecone, Inc", "license": "MIT", diff --git a/libs/langchain-qdrant/package.json b/libs/langchain-qdrant/package.json index 40548cfe9e44..b67aaa635d4c 100644 --- a/libs/langchain-qdrant/package.json +++ b/libs/langchain-qdrant/package.json @@ -15,11 +15,7 @@ "homepage": "https://github.com/langchain-ai/langchainjs/tree/main/libs/langchain-qdrant", "scripts": { "build": "yarn turbo:command build:internal --filter=@langchain/qdrant", - "build:internal": "yarn lc_build_v2 --create-entrypoints --pre --tree-shaking", - "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 && yarn move-cjs-to-dist && rm -rf dist-cjs", - "build:watch": "yarn create-entrypoints && tsc --outDir dist/ --watch", - "build:scripts": "yarn create-entrypoints && yarn check-tree-shaking", + "build:internal": "yarn lc_build --create-entrypoints --pre --tree-shaking", "lint:eslint": "NODE_OPTIONS=--max-old-space-size=4096 eslint --cache --ext .ts,.js src/", "lint:dpdm": "dpdm --exit-code circular:1 --no-warning --no-tree src/*.ts src/**/*.ts", "lint": "yarn lint:eslint && yarn lint:dpdm", @@ -31,10 +27,7 @@ "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 --config .prettierrc --write \"src\"", - "format:check": "prettier --config .prettierrc --check \"src\"", - "move-cjs-to-dist": "yarn lc-build --config ./langchain.config.js --move-cjs-dist", - "create-entrypoints": "yarn lc-build --config ./langchain.config.js --create-entrypoints", - "check-tree-shaking": "yarn lc-build --config ./langchain.config.js --tree-shaking" + "format:check": "prettier --config .prettierrc --check \"src\"" }, "author": "LangChain", "license": "MIT", diff --git a/libs/langchain-redis/package.json b/libs/langchain-redis/package.json index fda7d74d59a6..d321f4e47a42 100644 --- a/libs/langchain-redis/package.json +++ b/libs/langchain-redis/package.json @@ -15,11 +15,7 @@ "homepage": "https://github.com/langchain-ai/langchainjs/tree/main/libs/langchain-redis/", "scripts": { "build": "yarn turbo:command build:internal --filter=@langchain/redis", - "build:internal": "yarn lc_build_v2 --create-entrypoints --pre --tree-shaking", - "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 && yarn move-cjs-to-dist && rm -rf dist-cjs", - "build:watch": "yarn create-entrypoints && tsc --outDir dist/ --watch", - "build:scripts": "yarn create-entrypoints && yarn check-tree-shaking", + "build:internal": "yarn lc_build --create-entrypoints --pre --tree-shaking", "lint:eslint": "NODE_OPTIONS=--max-old-space-size=4096 eslint --cache --ext .ts,.js src/", "lint:dpdm": "dpdm --exit-code circular:1 --no-warning --no-tree src/*.ts src/**/*.ts", "lint": "yarn lint:eslint && yarn lint:dpdm", @@ -31,10 +27,7 @@ "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 --config .prettierrc --write \"src\"", - "format:check": "prettier --config .prettierrc --check \"src\"", - "move-cjs-to-dist": "yarn lc-build --config ./langchain.config.js --move-cjs-dist", - "create-entrypoints": "yarn lc-build --config ./langchain.config.js --create-entrypoints", - "check-tree-shaking": "yarn lc-build --config ./langchain.config.js --tree-shaking" + "format:check": "prettier --config .prettierrc --check \"src\"" }, "author": "LangChain", "license": "MIT", diff --git a/libs/langchain-scripts/bin/build.js b/libs/langchain-scripts/bin/build.js index 40b80ac0d611..ec93df06484c 100755 --- a/libs/langchain-scripts/bin/build.js +++ b/libs/langchain-scripts/bin/build.js @@ -1,911 +1 @@ -#!/usr/bin/env node - -import { Command } from "commander"; -import * as fs from "node:fs"; -import path from "node:path"; -import ts from "typescript"; -import { rollup } from "rollup"; - -/** - * @typedef {Object} ExtraImportMapEntry - * @property {Array} modules - * @property {Array} alias - * @property {string} path - */ - -/** - * @typedef {Object} ImportData - * @property {Record} exportedAliases - * @property {Record} imports - */ - -/** - * @param {any} obj - * @returns {obj is LangChainConfig} - */ -function _verifyObjectIsLangChainConfig( - obj -) { - if (typeof obj !== "object") { - console.error("LangChain config file is not an object"); - return false; - } - if ( - !("entrypoints" in obj) || - !("tsConfigPath" in obj) || - !("cjsSource" in obj) || - !("cjsDestination" in obj) || - !("abs" in obj) - ) { - console.error(`LangChain config file is missing required fields. One of: entrypoints, tsConfigPath, cjsSource, cjsDestination, abs`); - return false; - } - if (typeof obj.entrypoints !== "object") { - console.error("entrypoints field in LangChain config file is not an object"); - return false; - } - if (Object.values(obj.entrypoints).some((v) => typeof v !== "string")) { - console.error("entrypoints field in LangChain config file is not an object of strings"); - return false; - } - if ( - typeof obj.tsConfigPath !== "string" || - typeof obj.cjsSource !== "string" || - typeof obj.cjsDestination !== "string" - ) { - console.error("tsConfigPath, cjsSource, or cjsDestination fields in LangChain config file are not strings"); - return false; - } - if (typeof obj.abs !== "function") { - console.error("abs field in LangChain config file is not a function"); - return false; - } - - // Optional fields - if ( - "requiresOptionalDependency" in obj && - (!Array.isArray(obj.requiresOptionalDependency) || - obj.requiresOptionalDependency.some((v) => typeof v !== "string")) - ) { - console.error("requiresOptionalDependency field in LangChain config file is not an array of strings"); - return false; - } - if ( - "deprecatedNodeOnly" in obj && - (!Array.isArray(obj.deprecatedNodeOnly) || - obj.deprecatedNodeOnly.some((v) => typeof v !== "string")) - ) { - console.error("deprecatedNodeOnly field in LangChain config file is not an array of strings"); - return false; - } - if ( - "deprecatedOmitFromImportMap" in obj && - (!Array.isArray(obj.deprecatedOmitFromImportMap) || - obj.deprecatedOmitFromImportMap.some((v) => typeof v !== "string")) - ) { - console.error("deprecatedOmitFromImportMap field in LangChain config file is not an array of strings"); - return false; - } - if ("packageSuffix" in obj && typeof obj.packageSuffix !== "string") { - console.error("packageSuffix field in LangChain config file is not a string"); - return false; - } - if ( - "shouldTestExports" in obj && - typeof obj.shouldTestExports !== "boolean" - ) { - console.error("shouldTestExports field in LangChain config file is not a boolean"); - return false; - } - if ( - "extraImportMapEntries" in obj && - !Array.isArray(obj.extraImportMapEntries) - ) { - console.error("extraImportMapEntries field in LangChain config file is not an array"); - return false; - } - if ( - "gitignorePaths" in obj && - (!Array.isArray(obj.gitignorePaths) || - obj.gitignorePaths.some((v) => typeof v !== "string")) - ) { - console.error("gitignorePaths field in LangChain config file is not an array of strings"); - return false; - } - if ("internals" in obj && !Array.isArray(obj.internals)) { - console.error("internals field in LangChain config file is not an array"); - return false; - } - return true; -} - - -async function moveAndRename({ - /** @type {string} */ - source, - /** @type {string} */ - dest, - /** @type {(p: string) => string} */ - abs, -}) { - try { - for (const file of await fs.promises.readdir(abs(source), { withFileTypes: true })) { - if (file.isDirectory()) { - await moveAndRename({ - source: `${source}/${file.name}`, - dest: `${dest}/${file.name}`, - abs, - }); - } else if (file.isFile()) { - const parsed = path.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 fs.promises.readFile(abs(`${source}/${file.name}`), "utf8"); - const rewritten = content.replace( - /require\("(\..+?).js"\)/g, - (_, p1) => `require("${p1}.cjs")` - ); - - // Rename the file to .cjs - const renamed = path.format({ name: parsed.name, ext: ".cjs" }); - - await fs.promises.writeFile(abs(`${dest}/${renamed}`), rewritten, "utf8"); - } - } - } catch (err) { - console.error(err); - process.exit(1); - } -} - -/** @returns {Promise} */ -async function getPackageJson() { - return JSON.parse(await fs.promises.readFile("package.json", "utf-8")); -} - -async function listEntrypoints() { - const { exports } = await getPackageJson(); - /** @type {Record | null} */ - const exportsWithoutPackageJSON = exports - ? Object.entries(exports) - .filter(([k]) => k !== "./package.json") - .reduce((acc, [k, v]) => ({ ...acc, [k]: v }), {}) - : null; - - if (!exportsWithoutPackageJSON) { - throw new Error("No exports found in package.json"); - } - /** @type {string[]} */ - const entrypoints = []; - - for (const [key, value] of Object.entries(exportsWithoutPackageJSON)) { - if (key === "./package.json") { - continue; - } - if (typeof value === "string") { - entrypoints.push(value); - } else if ( - "import" in value && - value.import && - typeof value.import === "string" - ) { - entrypoints.push(value.import); - } - } - - return entrypoints; -} - -/** - * - * @param {Array} extraInternals - * @default [...Object.keys(packageJson.dependencies ?? {}), ...Object.keys(packageJson.peerDependencies ?? {})] - * @returns {Promise>} - */ -async function listExternals( - extraInternals -) { - const packageJson = await getPackageJson(); - return [ - ...Object.keys(packageJson.dependencies ?? {}), - ...Object.keys(packageJson.peerDependencies ?? {}), - ...extraInternals, - ]; -} - -/** - * - * @param {undefined | TreeShakingArgs} options - */ -export async function checkTreeShaking(options) { - const externals = await listExternals(options?.extraInternals ?? []); - const entrypoints = await listEntrypoints(); - const consoleInfo = console.info; - const reportMap = new Map(); - - for (const entrypoint of entrypoints) { - let sideEffects = ""; - - console.info = function (...args) { - const line = args.length ? args.join(" ") : ""; - if (line.includes("First side effect in")) { - sideEffects += `${line}\n`; - } - }; - - await rollup({ - external: externals, - input: entrypoint, - experimentalLogSideEffects: true, - }); - - let hasUnexpectedSideEffects = sideEffects.length > 0; - if (hasUnexpectedSideEffects) { - const entrypointContent = await fs.promises.readFile(`./dist/${entrypoint.replace(/^\.\//, "")}`); - // Allow escaping side effects strictly within code directly - // within an entrypoint - hasUnexpectedSideEffects = !entrypointContent - .toString() - .includes("/* __LC_ALLOW_ENTRYPOINT_SIDE_EFFECTS__ */"); - } - reportMap.set(entrypoint, { - log: sideEffects, - hasUnexpectedSideEffects, - }); - } - - console.info = consoleInfo; - - let failed = false; - for (const [entrypoint, report] of reportMap) { - if (report.hasUnexpectedSideEffects) { - failed = true; - console.log("---------------------------------"); - console.log(`Tree shaking failed for ${entrypoint}`); - console.log(report.log); - } - } - - if (failed) { - throw new Error("Tree shaking checks failed."); - } else { - console.log("Tree shaking checks passed!"); - } -} - - -function identifySecrets(absTsConfigPath) { - const secrets = new Set(); - - const tsConfig = ts.parseJsonConfigFileContent( - ts.readJsonConfigFile(absTsConfigPath, (p) => fs.readFileSync(p, "utf-8")), - ts.sys, - "./src/" - ); - - // `tsConfig.options.target` is not always defined when running this - // via the `@langchain/scripts` package. Instead, fallback to the raw - // tsConfig.json file contents. - const tsConfigFileContentsText = - "text" in tsConfig.raw - ? JSON.parse(tsConfig.raw.text) - : { compilerOptions: {} }; - - const tsConfigTarget = - tsConfig.options.target || tsConfigFileContentsText.compilerOptions.target; - - for (const fileName of tsConfig.fileNames.filter( - (fn) => !fn.endsWith("test.ts") - )) { - if (!tsConfigTarget) { - continue; - } - - const sourceFile = ts.createSourceFile( - fileName, - fs.readFileSync(fileName, "utf-8"), - tsConfigTarget, - 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 ( - ts.isGetAccessor(property) && - property.name.getText() === "lc_secrets" - ) { - // look for return { ... } - property.body?.statements.forEach((stmt) => { - if ( - ts.isReturnStatement(stmt) && - stmt.expression && - ts.isObjectLiteralExpression(stmt.expression) - ) { - stmt.expression.properties.forEach((element) => { - if (ts.isPropertyAssignment(element)) { - // Type guard for PropertyAssignment - if ( - element.initializer && - ts.isStringLiteral(element.initializer) - ) { - 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; - } - default: - break; - } - }); - break; - } - default: - break; - } - }); - } - - return secrets; -} - -// .gitignore -const DEFAULT_GITIGNORE_PATHS = ["node_modules", "dist", ".yarn"]; - -/** - * List of test-exports-* packages which we use to test that the exports field - * works correctly across different JS environments. - * Each entry is a tuple of [package name, import statement]. - * @type {Array<[string, (p: string) => string]>} - */ -const testExports = [ - [ - "test-exports-esm", - (p) => - `import * as ${p.replace(/\//g, "_")} from "langchain/${p}";`, - ], - [ - "test-exports-esbuild", - (p) => - `import * as ${p.replace(/\//g, "_")} from "langchain/${p}";`, - ], - [ - "test-exports-cjs", - (p) => - `const ${p.replace(/\//g, "_")} = require("langchain/${p}");`, - ], - ["test-exports-cf", (p) => `export * from "langchain/${p}";`], - ["test-exports-vercel", (p) => `export * from "langchain/${p}";`], - ["test-exports-vite", (p) => `export * from "langchain/${p}";`], - ["test-exports-bun", (p) => `export * from "langchain/${p}";`], -]; - -/** - * - * @param {string} relativePath - * @param {(json: Record) => Record} updateFunction - */ -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`); -}; - -/** - * @param {Record} entrypoints - * @returns {Record} - */ -const generateFiles = ( - entrypoints -) => { - const files = [...Object.entries(entrypoints)].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}'`], - [`${key}.d.cts`, `export * from '${compiledPath}'`], - ]; - }); - - return Object.fromEntries(files); -}; - -const updateConfig = ({ - /** @type {Record} */ - entrypoints, - /** @type {Array} */ - deprecatedNodeOnly, - /** @type {Array} */ - requiresOptionalDependency, - /** @type {boolean} */ - shouldTestExports, - /** @type {Array | undefined} */ - additionalGitignorePaths = [], -}) => { - const generatedFiles = generateFiles(entrypoints); - 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) => { - const entryPoint = { - types: { - import: `./${key}.d.ts`, - require: `./${key}.d.cts`, - default: `./${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); - }); - - const gitignorePaths = [...filenames, ...DEFAULT_GITIGNORE_PATHS, ...(additionalGitignorePaths ? additionalGitignorePaths : [])]; - - // Update .gitignore - fs.writeFileSync( - "./.gitignore", - `${gitignorePaths.join("\n")}\n` - ); - - if (shouldTestExports) { - // Update test-exports-*/entrypoints.js - const entrypointsToTest = Object.keys(entrypoints) - .filter((key) => !deprecatedNodeOnly.includes(key)) - .filter((key) => !requiresOptionalDependency.includes(key)); - testExports.forEach(([pkg, importStatement]) => { - const contents = `${entrypointsToTest - .map((key) => importStatement(key)) - .join("\n")}\n`; - fs.writeFileSync( - `../environment_tests/${pkg}/src/entrypoints.js`, - contents - ); - }); - } -}; - -const cleanGenerated = ({ - /** @type {Record} */ - entrypoints, -}) => { - const filenames = Object.keys(generateFiles(entrypoints)); - filenames.forEach((fname) => { - try { - fs.unlinkSync(fname); - } catch { - // ignore error - } - }); -}; - -// Tuple describing the auto-generated import map (used by langchain/load) -// [package name, import statement, import map path] -// This will not include entrypoints deprecated or requiring optional deps. -/** - * - * @param {string | null} packageSuffix - * @returns {[string, (k: string, p: string) => string, string]} - */ -const importMap = ( - packageSuffix -) => [ - `langchain${packageSuffix ? `-${packageSuffix}` : ""}`, - (k, p) => - `export * as ${k.replace(/\//g, "__")} from "../${p}.js";`, - "src/load/import_map.ts", - ]; - -const generateImportMap = ({ - /** @type {Record} */ - entrypoints, - /** @type {Array} */ - requiresOptionalDependency, - /** @type {Array} */ - deprecatedNodeOnly, - /** @type {Array} */ - deprecatedOmitFromImportMap, - /** @type {string | null} */ - packageSuffix, - /** @type {Array} */ - extraImportMapEntries, -}) => { - // Generate import map - const entrypointsToInclude = Object.keys(entrypoints) - .filter((key) => key !== "load") - .filter((key) => !deprecatedNodeOnly.includes(key)) - .filter((key) => !requiresOptionalDependency.includes(key)) - .filter((key) => !deprecatedOmitFromImportMap.includes(key)); - const [pkg, importStatement, importMapPath] = importMap(packageSuffix); - const contents = `${entrypointsToInclude - .map((key) => importStatement(key, entrypoints[key])) - .join("\n")}\n`; - const extraImportData = extraImportMapEntries.reduce( - (data, { modules, alias, path }) => { - const newData = { ...data }; - if (!newData.imports[path]) { - newData.imports[path] = []; - } - newData.imports[path] = [ - ...new Set(newData.imports[path].concat(modules)), - ]; - const exportAlias = alias.join("__"); - if (!newData.exportedAliases[exportAlias]) { - newData.exportedAliases[exportAlias] = []; - } - newData.exportedAliases[exportAlias] = - newData.exportedAliases[exportAlias].concat(modules); - return newData; - }, - { - imports: {}, - exportedAliases: {}, - } - ); - const extraImportStatements = Object.entries(extraImportData.imports).map( - ([path, modules]) => - `import {\n ${modules.join(",\n ")}\n} from "${path}";` - ); - const extraDeclarations = Object.entries(extraImportData.exportedAliases).map( - ([exportAlias, modules]) => - [ - `const ${exportAlias} = {\n ${modules.join(",\n ")}\n};`, - `export { ${exportAlias} };`, - ].join("\n") - ); - let extraContent = `${extraImportStatements.join( - "\n" - )}\n${extraDeclarations.join("\n")}\n`; - - extraContent.trim(); - if (!/[a-zA-Z0-9]/.test(extraContent)) { - extraContent = "" - } - - fs.writeFileSync( - `../${pkg}/${importMapPath}`, - `// Auto-generated by \`scripts/create-entrypoints.js\`. Do not edit manually.\n\n${contents}${extraContent}` - ); -}; - -/** - * - * @param {string | null} packageSuffix - * @returns {[string, string]} - */ -const importTypes = (packageSuffix) => [ - `langchain${packageSuffix ? `-${packageSuffix}` : ""}`, - "src/load/import_type.ts", -]; - -const generateImportTypes = ({ - /** @type {string} */ - absTsConfigPath, - /** @type {string | null} */ - packageSuffix, -}) => { - // Generate import types - const [pkg, importTypesPath] = importTypes(packageSuffix); - - fs.writeFileSync( - `../${pkg}/${importTypesPath}`, - `// Auto-generated by \`scripts/create-entrypoints.js\`. Do not edit manually. - -export interface OptionalImportMap {} - -export interface SecretMap { -${[...identifySecrets(absTsConfigPath)] - .sort() - .map((secret) => ` ${secret}?: string;`) - .join("\n")} -} -` - ); -}; - -/** - * @param {string | null} packageSuffix - * @returns {[string, (k: string) => string, string]} - */ -const importConstants = ( - packageSuffix -) => [ - `langchain${packageSuffix ? `-${packageSuffix}` : ""}`, - (k) => - ` "langchain${packageSuffix ? `_${packageSuffix}` : ""}/${k}"`, - "src/load/import_constants.ts", - ]; - -const generateImportConstants = ({ - /** @type {Record} */ - entrypoints, - /** @type {Array} */ - requiresOptionalDependency, - /** @type {Array} */ - deprecatedNodeOnly, - /** @type {string | null} */ - packageSuffix, -}) => { - // Generate import constants - const entrypointsToInclude = Object.keys(entrypoints) - .filter((key) => !deprecatedNodeOnly.includes(key)) - .filter((key) => requiresOptionalDependency.includes(key)); - const [pkg, importStatement, importConstantsPath] = - importConstants(packageSuffix); - const contents = - entrypointsToInclude.length > 0 - ? `\n${entrypointsToInclude - .map((key) => importStatement(key)) - .join(",\n")},\n];\n` - : "];\n"; - fs.writeFileSync( - `../${pkg}/${importConstantsPath}`, - `// Auto-generated by \`scripts/create-entrypoints.js\`. Do not edit manually.\n\nexport const optionalImportEntrypoints: string[] = [${contents}` - ); -}; - -export function createEntrypoints({ - /** - * 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. - * @type {Record} - */ - entrypoints, - /** - * Entrypoints in this list require an optional dependency to be installed. - * Therefore they are not tested in the generated test-exports-* packages. - * @type {undefined | string[]} - */ - requiresOptionalDependency = [], - /** - * Entrypoints in this list will - * 1. Be excluded from the documentation - * 2. Be only available in Node.js environments (for backwards compatibility) - * @type {undefined | string[]} - */ - deprecatedNodeOnly = [], - /** - * Endpoints that are deprecated due to redundancy. Will not appear in the import map. - * @type {string[]} - */ - deprecatedOmitFromImportMap = [], - /** - * The suffix of the package. Eg. `community` for `@langchain/community`. - * Used in the generated import map. - * @type {undefined | string} - */ - packageSuffix, - /** - * Whether or not to write to the test exports files. At the moment this only - * applies to the `langchain` package. - * @type {undefined | boolean} - */ - shouldTestExports = false, - /** - * Extra entries to add to the import map. - * @type {undefined | Array} - */ - extraImportMapEntries = [], - /** - * The absolute path to the tsconfig.json file. - * @type {string} - */ - absTsConfigPath, - /** - * Whether or not the pre command was passed. - * @type {boolean} - */ - isPre, - /** - * Whether or not to generate import maps - * @type {boolean} - */ - shouldGenMaps, - /** - * Additional paths to add to the .gitignore file. - * @type {Array | undefined} - */ - additionalGitignorePaths, -}) { - if (isPre) { - cleanGenerated({ entrypoints }); - if (shouldGenMaps) { - generateImportMap({ - entrypoints, - requiresOptionalDependency, - deprecatedNodeOnly, - deprecatedOmitFromImportMap, - packageSuffix: packageSuffix ?? null, - extraImportMapEntries, - }); - generateImportTypes({ - absTsConfigPath, - packageSuffix: packageSuffix ?? null, - }); - generateImportConstants({ - entrypoints, - requiresOptionalDependency, - deprecatedNodeOnly, - packageSuffix: packageSuffix ?? null, - }); - } - } else { - updateConfig({ - entrypoints, - deprecatedNodeOnly, - requiresOptionalDependency, - shouldTestExports, - additionalGitignorePaths, - }); - } -} - - -// --------SCRIPT CONTENT-------- - -async function main() { - const program = new Command(); - program - .description("Run a build script for a LangChain package.") - .option( - "--config ", - "Path to the config file, defaults to ./langchain.config.js" - ) - .option( - "--create-entrypoints", - "Pass only if you want to create entrypoints" - ) - .option("--tree-shaking", "Pass only if you want to check tree shaking") - .option("--move-cjs-dist", "Pass only if you want to move cjs to dist") - .option("--pre") - .option("--gen-maps"); - - program.parse(); - - const options = program.opts(); - - const shouldCreateEntrypoints = options.createEntrypoints; - const shouldCheckTreeShaking = options.treeShaking; - const shouldMoveCjsDist = options.moveCjsDist; - const isPre = options.pre; - const shouldGenMaps = options.genMaps; - const configFilePath = options.config ?? "./langchain.config.js"; - const resolvedConfigPath = path.resolve(process.cwd(), configFilePath); - - /** @type {LangChainConfig} */ - let config; - try { - const { config: lcConfig } = await import(resolvedConfigPath); - if (!_verifyObjectIsLangChainConfig(lcConfig)) { - throw new Error("Invalid config object."); - } - config = lcConfig; - } catch (e) { - console.error( - `Failed to read config file at path: ${configFilePath}.\n\n${e}` - ); - process.exit(1); - } - - if ( - [shouldCreateEntrypoints, shouldCheckTreeShaking, shouldMoveCjsDist].filter( - Boolean - ).length > 1 - ) { - console.error( - "Can only run one script at a time. Please pass only one of --create-entrypoints, --tree-shaking, --move-cjs-dist" - ); - process.exit(1); - } - - if ( - [shouldCreateEntrypoints, shouldCheckTreeShaking, shouldMoveCjsDist].filter( - Boolean - ).length === 0 - ) { - console.error( - "No script specified. Please pass one of --create-entrypoints, --tree-shaking, --move-cjs-dist" - ); - process.exit(1); - } - - if ( - (isPre || shouldGenMaps) && - [shouldCheckTreeShaking, shouldMoveCjsDist].filter(Boolean).length >= 1 - ) { - console.error( - "Can not pass --pre or --gen-maps with --tree-shaking or --move-cjs-dist" - ); - process.exit(1); - } - - if (shouldCreateEntrypoints) { - createEntrypoints({ - entrypoints: config.entrypoints, - requiresOptionalDependency: config.requiresOptionalDependency, - deprecatedNodeOnly: config.deprecatedNodeOnly, - deprecatedOmitFromImportMap: config.deprecatedOmitFromImportMap, - packageSuffix: config.packageSuffix, - shouldTestExports: config.shouldTestExports, - extraImportMapEntries: config.extraImportMapEntries, - absTsConfigPath: config.tsConfigPath, - isPre, - shouldGenMaps, - additionalGitignorePaths: config.additionalGitignorePaths, - }); - } - - if (shouldCheckTreeShaking) { - await checkTreeShaking({ - extraInternals: config.internals, - }); - } - - if (shouldMoveCjsDist) { - await moveAndRename({ - source: config.cjsSource, - dest: config.cjsDestination, - abs: config.abs, - }); - } -} - -/* #__PURE__ */ main().catch((e) => { - console.error(e); - process.exit(1); -}); +import "../dist/build/index.js"; diff --git a/libs/langchain-scripts/bin/build_v2.js b/libs/langchain-scripts/bin/build_v2.js deleted file mode 100755 index 0e57864f2fe0..000000000000 --- a/libs/langchain-scripts/bin/build_v2.js +++ /dev/null @@ -1 +0,0 @@ -import "../dist/build_v2.js"; diff --git a/libs/langchain-scripts/langchain.config.js b/libs/langchain-scripts/langchain.config.js index 64ea4089853a..1d83aa030070 100644 --- a/libs/langchain-scripts/langchain.config.js +++ b/libs/langchain-scripts/langchain.config.js @@ -13,7 +13,7 @@ export const config = { internals: [/node\:/], entrypoints: { index: "index", - build: "build", + build: "build/index", migrations: "migrations/index", check_broken_links: "check_broken_links", }, diff --git a/libs/langchain-scripts/package.json b/libs/langchain-scripts/package.json index bd069a48e35f..db77d0a36d68 100644 --- a/libs/langchain-scripts/package.json +++ b/libs/langchain-scripts/package.json @@ -14,15 +14,14 @@ }, "homepage": "https://github.com/langchain-ai/langchainjs/tree/main/libs/langchain-scripts/", "bin": { - "lc-build": "bin/build.js", - "lc_build_v2": "bin/build_v2.js", + "lc_build": "bin/build.js", "notebook_validate": "bin/validate_notebook.js" }, "scripts": { - "build": "yarn turbo:command build:internal --filter=@langchain/scripts", + "build": "yarn clean && yarn turbo:command build:internal --filter=@langchain/scripts", "build:internal": "tsc --project ./tsconfig.build.json && yarn move:artifacts && yarn build:generated", "move:artifacts": "mkdir -p ./dist && mv ./dist_build/* ./dist/", - "build:generated": "node bin/build_v2.js --create-entrypoints --pre --tree-shaking", + "build:generated": "node bin/build.js --create-entrypoints --pre --tree-shaking", "build:turbo": "yarn turbo:command build --filter=@langchain/scripts", "lint:eslint": "NODE_OPTIONS=--max-old-space-size=4096 eslint --cache --ext .ts,.js src/", "lint:dpdm": "dpdm --exit-code circular:1 --no-warning --no-tree src/*.ts src/**/*.ts", diff --git a/libs/langchain-scripts/src/build.ts b/libs/langchain-scripts/src/build.ts deleted file mode 100644 index 64468ed968e3..000000000000 --- a/libs/langchain-scripts/src/build.ts +++ /dev/null @@ -1,121 +0,0 @@ -#!/usr/bin/env node - -import { Command } from "commander"; -import path from "node:path"; -import { createEntrypoints } from "./create-entrypoints.js"; -import { checkTreeShaking } from "./check-tree-shaking.js"; -import { moveAndRename } from "./move-cjs-to-dist.js"; -import type { LangChainConfig } from "./types.js"; -import { _verifyObjectIsLangChainConfig } from "./utils.js"; - -export type { LangChainConfig } from "./types.js"; - -async function main() { - const program = new Command(); - program - .description("Run a build script for a LangChain package.") - .option( - "--config ", - "Path to the config file, defaults to ./langchain.config.js" - ) - .option( - "--create-entrypoints", - "Pass only if you want to create entrypoints" - ) - .option("--tree-shaking", "Pass only if you want to check tree shaking") - .option("--move-cjs-dist", "Pass only if you want to move cjs to dist") - .option("--pre") - .option("--gen-maps"); - - program.parse(); - - const options = program.opts(); - - const shouldCreateEntrypoints = options.createEntrypoints; - const shouldCheckTreeShaking = options.treeShaking; - const shouldMoveCjsDist = options.moveCjsDist; - const isPre = options.pre; - const shouldGenMaps = options.genMaps; - const configFilePath = options.config ?? "./langchain.config.js"; - const resolvedConfigPath = path.resolve(process.cwd(), configFilePath); - - let config: LangChainConfig; - try { - const { config: lcConfig } = await import(resolvedConfigPath); - if (!_verifyObjectIsLangChainConfig(lcConfig)) { - throw new Error("Invalid config object."); - } - config = lcConfig; - } catch (e) { - console.error( - `Failed to read config file at path: ${configFilePath}.\n\n${e}` - ); - process.exit(1); - } - - if ( - [shouldCreateEntrypoints, shouldCheckTreeShaking, shouldMoveCjsDist].filter( - Boolean - ).length > 1 - ) { - console.error( - "Can only run one script at a time. Please pass only one of --create-entrypoints, --tree-shaking, --move-cjs-dist" - ); - process.exit(1); - } - - if ( - [shouldCreateEntrypoints, shouldCheckTreeShaking, shouldMoveCjsDist].filter( - Boolean - ).length === 0 - ) { - console.error( - "No script specified. Please pass one of --create-entrypoints, --tree-shaking, --move-cjs-dist" - ); - process.exit(1); - } - - if ( - (isPre || shouldGenMaps) && - [shouldCheckTreeShaking, shouldMoveCjsDist].filter(Boolean).length >= 1 - ) { - console.error( - "Can not pass --pre or --gen-maps with --tree-shaking or --move-cjs-dist" - ); - process.exit(1); - } - - if (shouldCreateEntrypoints) { - createEntrypoints({ - entrypoints: config.entrypoints, - requiresOptionalDependency: config.requiresOptionalDependency, - deprecatedNodeOnly: config.deprecatedNodeOnly, - deprecatedOmitFromImportMap: config.deprecatedOmitFromImportMap, - packageSuffix: config.packageSuffix, - shouldTestExports: config.shouldTestExports, - extraImportMapEntries: config.extraImportMapEntries, - absTsConfigPath: config.tsConfigPath, - isPre, - shouldGenMaps, - }); - } - - if (shouldCheckTreeShaking) { - await checkTreeShaking({ - extraInternals: config.internals, - }); - } - - if (shouldMoveCjsDist) { - await moveAndRename({ - source: config.cjsSource, - dest: config.cjsDestination, - abs: config.abs, - }); - } -} - -/* #__PURE__ */ main().catch((e) => { - console.error(e); - process.exit(1); -}); diff --git a/libs/langchain-scripts/src/build_v2.ts b/libs/langchain-scripts/src/build/index.ts similarity index 93% rename from libs/langchain-scripts/src/build_v2.ts rename to libs/langchain-scripts/src/build/index.ts index 4c4b42653c60..0efb6f720379 100644 --- a/libs/langchain-scripts/src/build_v2.ts +++ b/libs/langchain-scripts/src/build/index.ts @@ -5,7 +5,8 @@ import { Command } from "commander"; import { rollup } from "@rollup/wasm-node"; import path from "node:path"; import { glob } from "glob"; -import { ExportsMapValue, ImportData, LangChainConfig } from "./types.js"; +import { setTimeout } from "node:timers/promises"; +import { ExportsMapValue, ImportData, LangChainConfig } from "../types.js"; async function asyncSpawn(command: string, args: string[]) { return new Promise((resolve, reject) => { @@ -28,47 +29,55 @@ async function asyncSpawn(command: string, args: string[]) { }); } -const deleteFolderRecursive = async function (inputPath: string) { - try { - // Verify the path exists - if ( - await fs.promises - .access(inputPath) - .then(() => true) - .catch(() => false) - ) { - const pathStat = await fs.promises.lstat(inputPath); - // If it's a file, delete it and return - if (pathStat.isFile()) { - await fs.promises.unlink(inputPath); - } else if (pathStat.isDirectory()) { - // List contents of directory - const directoryContents = await fs.promises.readdir(inputPath); - if (directoryContents.length) { - for await (const item of directoryContents) { - const itemStat = await fs.promises.lstat( - path.join(inputPath, item) - ); - if (itemStat.isFile()) { - // Delete file - await fs.promises.unlink(path.join(inputPath, item)); - } else if (itemStat.isDirectory()) { - await deleteFolderRecursive(path.join(inputPath, item)); +const deleteFolderRecursive = async function ( + inputPath: string, + retries = 3, + delay = 100 +) { + for (let attempt = 0; attempt < retries; attempt += 1) { + try { + // Verify the path exists + if ( + await fs.promises + .access(inputPath) + .then(() => true) + .catch(() => false) + ) { + const pathStat = await fs.promises.lstat(inputPath); + // If it's a file, delete it and return + if (pathStat.isFile()) { + await fs.promises.unlink(inputPath); + } else if (pathStat.isDirectory()) { + // List contents of directory + const directoryContents = await fs.promises.readdir(inputPath); + if (directoryContents.length) { + for await (const item of directoryContents) { + await deleteFolderRecursive( + path.join(inputPath, item), + retries, + delay + ); } } - } else if (directoryContents.length === 0) { - // If the directory is empty, delete it + // If the directory is empty or all contents have been deleted, delete it await fs.promises.rmdir(inputPath); } } + // If we reach here, the operation was successful + return; + // eslint-disable-next-line @typescript-eslint/no-explicit-any + } catch (error: any) { + if (error.code === "ENOENT") { + // File or directory doesn't exist, consider it deleted + return; + } + if (attempt === retries - 1) { + // If this was the last attempt, throw the error + throw error; + } + // Wait before the next attempt + await setTimeout(delay); } - // eslint-disable-next-line @typescript-eslint/no-explicit-any - } catch (error: any) { - if (error.code !== "ENOENT") { - // If the error is not "file or directory doesn't exist", rethrow it - throw error; - } - // Otherwise, ignore the error (file or directory already doesn't exist) } }; diff --git a/libs/langchain-scripts/src/utils.ts b/libs/langchain-scripts/src/build/utils.ts similarity index 98% rename from libs/langchain-scripts/src/utils.ts rename to libs/langchain-scripts/src/build/utils.ts index 2189cdfa2c29..1f2210f87a36 100644 --- a/libs/langchain-scripts/src/utils.ts +++ b/libs/langchain-scripts/src/build/utils.ts @@ -1,4 +1,4 @@ -import { LangChainConfig } from "./types.js"; +import { LangChainConfig } from "../types.js"; export function _verifyObjectIsLangChainConfig( // eslint-disable-next-line @typescript-eslint/no-explicit-any diff --git a/libs/langchain-scripts/src/check-tree-shaking.ts b/libs/langchain-scripts/src/check-tree-shaking.ts deleted file mode 100644 index cabec3b60200..000000000000 --- a/libs/langchain-scripts/src/check-tree-shaking.ts +++ /dev/null @@ -1,122 +0,0 @@ -import fs from "node:fs/promises"; -import { rollup } from "rollup"; -import { ExportsMapValue, PackageJSON, TreeShakingArgs } from "./types.js"; - -async function getPackageJson(): Promise { - return JSON.parse(await fs.readFile("package.json", "utf-8")); -} - -async function listEntrypoints() { - const { exports } = await getPackageJson(); - const exportsWithoutPackageJSON: Record< - string, - ExportsMapValue | string - > | null = exports - ? Object.entries(exports) - .filter(([k]) => k !== "./package.json") - .reduce((acc, [k, v]) => ({ ...acc, [k]: v }), {}) - : null; - - if (!exportsWithoutPackageJSON) { - throw new Error("No exports found in package.json"); - } - const entrypoints: string[] = []; - - for (const [key, value] of Object.entries(exportsWithoutPackageJSON)) { - if (key === "./package.json") { - continue; - } - if (typeof value === "string") { - entrypoints.push(value); - } else if ( - "import" in value && - value.import && - typeof value.import === "string" - ) { - entrypoints.push(value.import); - } - } - - return entrypoints; -} - -/** - * - * @param {Array | undefined} extraInternals - * @default [...Object.keys(packageJson.dependencies ?? {}), ...Object.keys(packageJson.peerDependencies ?? {})] - * @returns {Promise>} - */ -async function listExternals( - extraInternals: Array -): Promise> { - const packageJson = await getPackageJson(); - return [ - ...Object.keys(packageJson.dependencies ?? {}), - ...Object.keys(packageJson.peerDependencies ?? {}), - ...extraInternals, - ]; -} - -export async function checkTreeShaking(options?: TreeShakingArgs) { - const externals = await listExternals(options?.extraInternals ?? []); - const entrypoints = await listEntrypoints(); - const consoleInfo = console.info; - const reportMap: Map< - string, - { - log: string; - hasUnexpectedSideEffects: boolean; - } - > = new Map(); - - for (const entrypoint of entrypoints) { - let sideEffects = ""; - - console.info = function (...args) { - const line = args.length ? args.join(" ") : ""; - if (line.includes("First side effect in")) { - sideEffects += `${line}\n`; - } - }; - - await rollup({ - external: externals, - input: entrypoint, - experimentalLogSideEffects: true, - }); - - let hasUnexpectedSideEffects = sideEffects.length > 0; - if (hasUnexpectedSideEffects) { - const entrypointContent = await fs.readFile( - `./dist/${entrypoint.replace(/^\.\//, "")}` - ); - // Allow escaping side effects strictly within code directly - // within an entrypoint - hasUnexpectedSideEffects = !entrypointContent - .toString() - .includes("/* __LC_ALLOW_ENTRYPOINT_SIDE_EFFECTS__ */"); - } - reportMap.set(entrypoint, { - log: sideEffects, - hasUnexpectedSideEffects, - }); - } - - console.info = consoleInfo; - - let failed = false; - for (const [entrypoint, report] of reportMap) { - if (report.hasUnexpectedSideEffects) { - failed = true; - console.log("---------------------------------"); - console.log(`Tree shaking failed for ${entrypoint}`); - console.log(report.log); - } - } - - if (failed) { - throw new Error("Tree shaking checks failed."); - } else { - console.log("Tree shaking checks passed!"); - } -} diff --git a/libs/langchain-scripts/src/create-entrypoints.ts b/libs/langchain-scripts/src/create-entrypoints.ts deleted file mode 100644 index 5c96d2bbfb4d..000000000000 --- a/libs/langchain-scripts/src/create-entrypoints.ts +++ /dev/null @@ -1,385 +0,0 @@ -import * as fs from "node:fs"; -import * as path from "node:path"; -import { identifySecrets } from "./identify-secrets.js"; -import type { ExtraImportMapEntry, ImportData } from "./types.js"; - -// .gitignore -const DEFAULT_GITIGNORE_PATHS = ["node_modules", "dist", ".yarn"]; - -// List of test-exports-* packages which we use to test that the exports field -// works correctly across different JS environments. -// Each entry is a tuple of [package name, import statement]. -const testExports: Array<[string, (p: string) => string]> = [ - [ - "test-exports-esm", - (p: string) => - `import * as ${p.replace(/\//g, "_")} from "langchain/${p}";`, - ], - [ - "test-exports-esbuild", - (p: string) => - `import * as ${p.replace(/\//g, "_")} from "langchain/${p}";`, - ], - [ - "test-exports-cjs", - (p: string) => - `const ${p.replace(/\//g, "_")} = require("langchain/${p}");`, - ], - ["test-exports-cf", (p: string) => `export * from "langchain/${p}";`], - ["test-exports-vercel", (p: string) => `export * from "langchain/${p}";`], - ["test-exports-vite", (p: string) => `export * from "langchain/${p}";`], - ["test-exports-bun", (p: string) => `export * from "langchain/${p}";`], -]; - -const updateJsonFile = ( - relativePath: string, - updateFunction: (json: Record) => Record -) => { - const contents = fs.readFileSync(relativePath).toString(); - const res = updateFunction(JSON.parse(contents)); - fs.writeFileSync(relativePath, `${JSON.stringify(res, null, 2)}\n`); -}; - -const generateFiles = ( - entrypoints: Record -): Record => { - const files = [...Object.entries(entrypoints)].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}'`], - [`${key}.d.cts`, `export * from '${compiledPath}'`], - ]; - }); - - return Object.fromEntries(files); -}; - -const updateConfig = ({ - entrypoints, - deprecatedNodeOnly, - requiresOptionalDependency, - shouldTestExports, -}: { - entrypoints: Record; - deprecatedNodeOnly: Array; - requiresOptionalDependency: Array; - shouldTestExports: boolean; -}) => { - const generatedFiles = generateFiles(entrypoints); - 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) => { - const entryPoint = { - types: { - import: `./${key}.d.ts`, - require: `./${key}.d.cts`, - default: `./${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` - ); - - if (shouldTestExports) { - // Update test-exports-*/entrypoints.js - const entrypointsToTest = Object.keys(entrypoints) - .filter((key) => !deprecatedNodeOnly.includes(key)) - .filter((key) => !requiresOptionalDependency.includes(key)); - testExports.forEach(([pkg, importStatement]) => { - const contents = `${entrypointsToTest - .map((key) => importStatement(key)) - .join("\n")}\n`; - fs.writeFileSync( - `../environment_tests/${pkg}/src/entrypoints.js`, - contents - ); - }); - } -}; - -const cleanGenerated = ({ - entrypoints, -}: { - entrypoints: Record; -}) => { - const filenames = Object.keys(generateFiles(entrypoints)); - filenames.forEach((fname) => { - try { - fs.unlinkSync(fname); - } catch { - // ignore error - } - }); -}; - -// Tuple describing the auto-generated import map (used by langchain/load) -// [package name, import statement, import map path] -const importMap = ( - packageSuffix: string | null -): [string, (k: string, p: string) => string, string] => [ - `langchain${packageSuffix ? `-${packageSuffix}` : ""}`, - (k: string, p: string) => - `export * as ${k.replace(/\//g, "__")} from "../${p}.js";`, - "src/load/import_map.ts", -]; - -const generateImportMap = ({ - entrypoints, - requiresOptionalDependency, - deprecatedNodeOnly, - deprecatedOmitFromImportMap, - packageSuffix, - extraImportMapEntries, -}: { - entrypoints: Record; - requiresOptionalDependency: Array; - deprecatedNodeOnly: Array; - deprecatedOmitFromImportMap: Array; - packageSuffix: string | null; - extraImportMapEntries: Array; -}) => { - // Generate import map - const entrypointsToInclude = Object.keys(entrypoints) - .filter((key) => key !== "load") - .filter((key) => !deprecatedNodeOnly.includes(key)) - .filter((key) => !requiresOptionalDependency.includes(key)) - .filter((key) => !deprecatedOmitFromImportMap.includes(key)); - const [pkg, importStatement, importMapPath] = importMap(packageSuffix); - const contents = `${entrypointsToInclude - .map((key) => importStatement(key, entrypoints[key])) - .join("\n")}\n`; - const extraImportData = extraImportMapEntries.reduce( - (data, { modules, alias, path }) => { - const newData = { ...data }; - if (!newData.imports[path]) { - newData.imports[path] = []; - } - newData.imports[path] = [ - ...new Set(newData.imports[path].concat(modules)), - ]; - const exportAlias = alias.join("__"); - if (!newData.exportedAliases[exportAlias]) { - newData.exportedAliases[exportAlias] = []; - } - newData.exportedAliases[exportAlias] = - newData.exportedAliases[exportAlias].concat(modules); - return newData; - }, - { - imports: {}, - exportedAliases: {}, - } - ); - const extraImportStatements = Object.entries(extraImportData.imports).map( - ([path, modules]) => - `import {\n ${modules.join(",\n ")}\n} from "${path}";` - ); - const extraDeclarations = Object.entries(extraImportData.exportedAliases).map( - ([exportAlias, modules]) => - [ - `const ${exportAlias} = {\n ${modules.join(",\n ")}\n};`, - `export { ${exportAlias} };`, - ].join("\n") - ); - const extraContent = `${extraImportStatements.join( - "\n" - )}\n${extraDeclarations.join("\n")}\n`; - fs.writeFileSync( - `../${pkg}/${importMapPath}`, - `// Auto-generated by \`scripts/create-entrypoints.js\`. Do not edit manually.\n\n${contents}${extraContent}` - ); -}; - -const importTypes = (packageSuffix: string | null): [string, string] => [ - `langchain${packageSuffix ? `-${packageSuffix}` : ""}`, - "src/load/import_type.ts", -]; - -const generateImportTypes = ({ - absTsConfigPath, - packageSuffix, -}: { - absTsConfigPath: string; - packageSuffix: string | null; -}) => { - // Generate import types - const [pkg, importTypesPath] = importTypes(packageSuffix); - - fs.writeFileSync( - `../${pkg}/${importTypesPath}`, - `// Auto-generated by \`scripts/create-entrypoints.js\`. Do not edit manually. - -export interface OptionalImportMap {} - -export interface SecretMap { -${[...identifySecrets(absTsConfigPath)] - .sort() - .map((secret) => ` ${secret}?: string;`) - .join("\n")} -} -` - ); -}; - -const importConstants = ( - packageSuffix: string | null -): [string, (k: string) => string, string] => [ - `langchain${packageSuffix ? `-${packageSuffix}` : ""}`, - (k: string) => - ` "langchain${packageSuffix ? `_${packageSuffix}` : ""}/${k}"`, - "src/load/import_constants.ts", -]; - -const generateImportConstants = ({ - entrypoints, - requiresOptionalDependency, - deprecatedNodeOnly, - packageSuffix, -}: { - entrypoints: Record; - requiresOptionalDependency: Array; - deprecatedNodeOnly: Array; - packageSuffix: string | null; -}) => { - // Generate import constants - const entrypointsToInclude = Object.keys(entrypoints) - .filter((key) => !deprecatedNodeOnly.includes(key)) - .filter((key) => requiresOptionalDependency.includes(key)); - const [pkg, importStatement, importConstantsPath] = - importConstants(packageSuffix); - const contents = - entrypointsToInclude.length > 0 - ? `\n${entrypointsToInclude - .map((key) => importStatement(key)) - .join(",\n")},\n];\n` - : "];\n"; - fs.writeFileSync( - `../${pkg}/${importConstantsPath}`, - `// Auto-generated by \`scripts/create-entrypoints.js\`. Do not edit manually.\n\nexport const optionalImportEntrypoints: string[] = [${contents}` - ); -}; - -export function createEntrypoints({ - entrypoints, - requiresOptionalDependency = [], - deprecatedNodeOnly = [], - deprecatedOmitFromImportMap = [], - packageSuffix, - shouldTestExports = false, - extraImportMapEntries = [], - absTsConfigPath, - isPre, - shouldGenMaps, -}: { - /** - * 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. - */ - entrypoints: Record; - /** - * Entrypoints in this list require an optional dependency to be installed. - * Therefore they are not tested in the generated test-exports-* packages. - */ - requiresOptionalDependency?: string[]; - /** - * Entrypoints in this list will - * 1. Be excluded from the documentation - * 2. Be only available in Node.js environments (for backwards compatibility) - */ - deprecatedNodeOnly?: string[]; - /** - * Endpoints that are deprecated due to redundancy. Will not appear in the import map. - */ - deprecatedOmitFromImportMap?: string[]; - /** - * The suffix of the package. Eg. `community` for `@langchain/community`. - * Used in the generated import map. - */ - packageSuffix?: string; - /** - * Whether or not to write to the test exports files. At the moment this only - * applies to the `langchain` package. - */ - shouldTestExports?: boolean; - /** - * Extra entries to add to the import map. - */ - extraImportMapEntries?: Array; - /** - * The absolute path to the tsconfig.json file. - */ - absTsConfigPath: string; - /** - * Whether or not the pre command was passed. - */ - isPre: boolean; - /** - * Whether or not to generate import maps - */ - shouldGenMaps: boolean; -}) { - if (isPre) { - cleanGenerated({ entrypoints }); - if (shouldGenMaps) { - generateImportMap({ - entrypoints, - requiresOptionalDependency, - deprecatedNodeOnly, - deprecatedOmitFromImportMap, - packageSuffix: packageSuffix ?? null, - extraImportMapEntries, - }); - generateImportTypes({ - absTsConfigPath, - packageSuffix: packageSuffix ?? null, - }); - generateImportConstants({ - entrypoints, - requiresOptionalDependency, - deprecatedNodeOnly, - packageSuffix: packageSuffix ?? null, - }); - } - } else { - updateConfig({ - entrypoints, - deprecatedNodeOnly, - requiresOptionalDependency, - shouldTestExports, - }); - } -} diff --git a/libs/langchain-scripts/src/identify-secrets.ts b/libs/langchain-scripts/src/identify-secrets.ts deleted file mode 100644 index fe26009acd9c..000000000000 --- a/libs/langchain-scripts/src/identify-secrets.ts +++ /dev/null @@ -1,100 +0,0 @@ -import ts from "typescript"; -import * as fs from "node:fs"; - -export function identifySecrets(absTsConfigPath: string) { - const secrets = new Set(); - - const tsConfig = ts.parseJsonConfigFileContent( - ts.readJsonConfigFile(absTsConfigPath, (p) => fs.readFileSync(p, "utf-8")), - ts.sys, - "./src/" - ); - - // `tsConfig.options.target` is not always defined when running this - // via the `@langchain/scripts` package. Instead, fallback to the raw - // tsConfig.json file contents. - const tsConfigFileContentsText = - "text" in tsConfig.raw - ? JSON.parse(tsConfig.raw.text as string) - : { compilerOptions: {} }; - - const tsConfigTarget = - tsConfig.options.target || tsConfigFileContentsText.compilerOptions.target; - - for (const fileName of tsConfig.fileNames.filter( - (fn) => !fn.endsWith("test.ts") - )) { - if (!tsConfigTarget) { - continue; - } - - const sourceFile = ts.createSourceFile( - fileName, - fs.readFileSync(fileName, "utf-8"), - tsConfigTarget, - 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 ( - ts.isGetAccessor(property) && - property.name.getText() === "lc_secrets" - ) { - // look for return { ... } - property.body?.statements.forEach((stmt) => { - if ( - ts.isReturnStatement(stmt) && - stmt.expression && - ts.isObjectLiteralExpression(stmt.expression) - ) { - stmt.expression.properties.forEach((element) => { - if (ts.isPropertyAssignment(element)) { - // Type guard for PropertyAssignment - if ( - element.initializer && - ts.isStringLiteral(element.initializer) - ) { - 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; - } - default: - break; - } - }); - break; - } - default: - break; - } - }); - } - - return secrets; -} diff --git a/libs/langchain-scripts/src/move-cjs-to-dist.ts b/libs/langchain-scripts/src/move-cjs-to-dist.ts deleted file mode 100644 index b809cb206ef6..000000000000 --- a/libs/langchain-scripts/src/move-cjs-to-dist.ts +++ /dev/null @@ -1,55 +0,0 @@ -import path from "node:path"; -import fs from "node:fs"; - -export async function moveAndRename({ - source, - dest, - abs, -}: { - source: string; - dest: string; - abs: (p: string) => string; -}) { - try { - for (const file of await fs.promises.readdir(abs(source), { - withFileTypes: true, - })) { - if (file.isDirectory()) { - await moveAndRename({ - source: `${source}/${file.name}`, - dest: `${dest}/${file.name}`, - abs, - }); - } else if (file.isFile()) { - const parsed = path.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 fs.promises.readFile( - abs(`${source}/${file.name}`), - "utf8" - ); - const rewritten = content.replace( - /require\("(\..+?).js"\)/g, - (_, p1) => `require("${p1}.cjs")` - ); - - // Rename the file to .cjs - const renamed = path.format({ name: parsed.name, ext: ".cjs" }); - - await fs.promises.writeFile( - abs(`${dest}/${renamed}`), - rewritten, - "utf8" - ); - } - } - } catch (err) { - console.error(err); - process.exit(1); - } -} diff --git a/libs/langchain-scripts/src/tests/config_file.test.ts b/libs/langchain-scripts/src/tests/config_file.test.ts index 2d1c64d5e92a..c1a92e109e07 100644 --- a/libs/langchain-scripts/src/tests/config_file.test.ts +++ b/libs/langchain-scripts/src/tests/config_file.test.ts @@ -1,5 +1,5 @@ import { test, expect } from "@jest/globals"; -import { _verifyObjectIsLangChainConfig } from "../utils.js"; +import { _verifyObjectIsLangChainConfig } from "../build/utils.js"; test("_verifyObjectIsLangChainConfig successfully fails an invalid object", async () => { const { config } = await import("./langchain.invalid.config.js"); diff --git a/libs/langchain-scripts/tsconfig.build.json b/libs/langchain-scripts/tsconfig.build.json index 191e76c228c2..23cde926083d 100644 --- a/libs/langchain-scripts/tsconfig.build.json +++ b/libs/langchain-scripts/tsconfig.build.json @@ -23,8 +23,7 @@ "strict": true }, "include": [ - "src/build_v2.ts", - "src/types.ts" + "src/build/*" ], "exclude": [ "node_modules/", diff --git a/libs/langchain-standard-tests/package.json b/libs/langchain-standard-tests/package.json index 529be1d4445c..2cedaaccd878 100644 --- a/libs/langchain-standard-tests/package.json +++ b/libs/langchain-standard-tests/package.json @@ -15,7 +15,7 @@ "homepage": "https://github.com/langchain-ai/langchainjs/tree/main/libs/langchain-standard-tests/", "scripts": { "build": "yarn turbo:command build:internal --filter=@langchain/standard-tests", - "build:internal": "yarn lc_build_v2 --create-entrypoints --pre --tree-shaking", + "build:internal": "yarn lc_build --create-entrypoints --pre --tree-shaking", "lint:eslint": "NODE_OPTIONS=--max-old-space-size=4096 eslint --cache --ext .ts,.js src/", "lint:dpdm": "dpdm --exit-code circular:1 --no-warning --no-tree src/*.ts src/**/*.ts", "lint": "yarn lint:eslint && yarn lint:dpdm", diff --git a/libs/langchain-textsplitters/package.json b/libs/langchain-textsplitters/package.json index 65869485c723..67bfea71f685 100644 --- a/libs/langchain-textsplitters/package.json +++ b/libs/langchain-textsplitters/package.json @@ -15,11 +15,7 @@ "homepage": "https://github.com/langchain-ai/langchainjs/tree/main/libs/langchain-textsplitters/", "scripts": { "build": "yarn turbo:command build:internal --filter=@langchain/textsplitters", - "build:internal": "yarn lc_build_v2 --create-entrypoints --pre --tree-shaking", - "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 && yarn move-cjs-to-dist && rm -rf dist-cjs", - "build:watch": "yarn create-entrypoints && tsc --outDir dist/ --watch", - "build:scripts": "yarn create-entrypoints && yarn check-tree-shaking", + "build:internal": "yarn lc_build --create-entrypoints --pre --tree-shaking", "lint:eslint": "NODE_OPTIONS=--max-old-space-size=4096 eslint --cache --ext .ts,.js src/", "lint:dpdm": "dpdm --exit-code circular:1 --no-warning --no-tree src/*.ts src/**/*.ts", "lint": "yarn lint:eslint && yarn lint:dpdm", @@ -31,10 +27,7 @@ "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 --config .prettierrc --write \"src\"", - "format:check": "prettier --config .prettierrc --check \"src\"", - "move-cjs-to-dist": "yarn lc-build --config ./langchain.config.js --move-cjs-dist", - "create-entrypoints": "yarn lc-build --config ./langchain.config.js --create-entrypoints", - "check-tree-shaking": "yarn lc-build --config ./langchain.config.js --tree-shaking" + "format:check": "prettier --config .prettierrc --check \"src\"" }, "author": "LangChain", "license": "MIT", diff --git a/libs/langchain-weaviate/package.json b/libs/langchain-weaviate/package.json index 521f99b02a31..c29d97674172 100644 --- a/libs/langchain-weaviate/package.json +++ b/libs/langchain-weaviate/package.json @@ -15,12 +15,7 @@ "homepage": "https://github.com/langchain-ai/langchainjs/tree/main/libs/langchain-weaviate/", "scripts": { "build": "yarn turbo:command build:internal --filter=@langchain/weaviate", - "build:internal": "yarn lc_build_v2 --create-entrypoints --pre --tree-shaking", - "build:deps": "yarn run turbo:command build --filter=@langchain/core", - "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 && yarn move-cjs-to-dist && rm -rf dist-cjs", - "build:watch": "yarn create-entrypoints && tsc --outDir dist/ --watch", - "build:scripts": "yarn create-entrypoints && yarn check-tree-shaking", + "build:internal": "yarn lc_build --create-entrypoints --pre --tree-shaking", "lint:eslint": "NODE_OPTIONS=--max-old-space-size=4096 eslint --cache --ext .ts,.js src/", "lint:dpdm": "dpdm --exit-code circular:1 --no-warning --no-tree src/*.ts src/**/*.ts", "lint": "yarn lint:eslint && yarn lint:dpdm", @@ -32,10 +27,7 @@ "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 --config .prettierrc --write \"src\"", - "format:check": "prettier --config .prettierrc --check \"src\"", - "move-cjs-to-dist": "yarn lc-build --config ./langchain.config.js --move-cjs-dist", - "create-entrypoints": "yarn lc-build --config ./langchain.config.js --create-entrypoints", - "check-tree-shaking": "yarn lc-build --config ./langchain.config.js --tree-shaking" + "format:check": "prettier --config .prettierrc --check \"src\"" }, "author": "LangChain", "license": "MIT", diff --git a/libs/langchain-yandex/package.json b/libs/langchain-yandex/package.json index 21e3cf3eb257..fce662e8c06d 100644 --- a/libs/langchain-yandex/package.json +++ b/libs/langchain-yandex/package.json @@ -15,27 +15,19 @@ "homepage": "https://github.com/langchain-ai/langchainjs/tree/main/libs/langchain-yandex/", "scripts": { "build": "yarn turbo:command build:internal --filter=@langchain/yandex", - "build:internal": "yarn lc_build_v2 --create-entrypoints --pre --tree-shaking", - "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 && yarn move-cjs-to-dist && rm -rf dist-cjs", - "build:watch": "yarn create-entrypoints && tsc --outDir dist/ --watch", - "build:scripts": "yarn create-entrypoints && yarn check-tree-shaking", + "build:internal": "yarn lc_build --create-entrypoints --pre --tree-shaking", "lint:eslint": "NODE_OPTIONS=--max-old-space-size=4096 eslint --cache --ext .ts,.js src/", "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": "rm -rf .turbo dist/", "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 --config .prettierrc --write \"src\"", - "format:check": "prettier --config .prettierrc --check \"src\"", - "move-cjs-to-dist": "yarn lc-build --config ./langchain.config.js --move-cjs-dist", - "create-entrypoints": "yarn lc-build --config ./langchain.config.js --create-entrypoints", - "check-tree-shaking": "yarn lc-build --config ./langchain.config.js --tree-shaking" + "format:check": "prettier --config .prettierrc --check \"src\"" }, "author": "LangChain", "license": "MIT", diff --git a/yarn.lock b/yarn.lock index b3c68d9ab100..cab6f3db4f10 100644 --- a/yarn.lock +++ b/yarn.lock @@ -12438,8 +12438,7 @@ __metadata: tsx: ^4.16.2 typescript: ^5.4.5 bin: - lc-build: bin/build.js - lc_build_v2: bin/build_v2.js + lc_build: bin/build.js notebook_validate: bin/validate_notebook.js languageName: unknown linkType: soft From bfe988410c7ab8c6e8b86c2d282c58a4a2c4a137 Mon Sep 17 00:00:00 2001 From: Jacob Lee Date: Thu, 22 Aug 2024 13:41:55 -0700 Subject: [PATCH 075/344] community[patch]: Release 0.2.29 (#6598) --- libs/langchain-community/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libs/langchain-community/package.json b/libs/langchain-community/package.json index 57fa2d83e33f..b6823d008750 100644 --- a/libs/langchain-community/package.json +++ b/libs/langchain-community/package.json @@ -1,6 +1,6 @@ { "name": "@langchain/community", - "version": "0.2.28", + "version": "0.2.29", "description": "Third-party integrations for LangChain.js", "type": "module", "engines": { From 3f23d9c0d164e518bf73af4954b6a30d7a1b6e55 Mon Sep 17 00:00:00 2001 From: Jacob Lee Date: Thu, 22 Aug 2024 14:01:09 -0700 Subject: [PATCH 076/344] community[patch]: Fix community langgraph dep (#6599) --- libs/langchain-community/package.json | 4 ++-- yarn.lock | 24 ++++++++++++------------ 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/libs/langchain-community/package.json b/libs/langchain-community/package.json index b6823d008750..e8f5d3f2d2f6 100644 --- a/libs/langchain-community/package.json +++ b/libs/langchain-community/package.json @@ -78,7 +78,7 @@ "@gradientai/nodejs-sdk": "^1.2.0", "@huggingface/inference": "^2.6.4", "@jest/globals": "^29.5.0", - "@langchain/langgraph": "*", + "@langchain/langgraph": "~0.0.26", "@langchain/scripts": "^0.0.23", "@langchain/standard-tests": "0.0.0", "@layerup/layerup-security": "^1.5.12", @@ -239,7 +239,7 @@ "@google-cloud/storage": "^6.10.1 || ^7.7.0", "@gradientai/nodejs-sdk": "^1.2.0", "@huggingface/inference": "^2.6.4", - "@langchain/langgraph": "~0.0.26", + "@langchain/langgraph": "*", "@layerup/layerup-security": "^1.5.12", "@mendable/firecrawl-js": "^0.0.13", "@mlc-ai/web-llm": "0.2.46", diff --git a/yarn.lock b/yarn.lock index cab6f3db4f10..c37553989950 100644 --- a/yarn.lock +++ b/yarn.lock @@ -11179,7 +11179,7 @@ __metadata: "@huggingface/inference": ^2.6.4 "@jest/globals": ^29.5.0 "@langchain/core": ">=0.2.21 <0.3.0" - "@langchain/langgraph": "*" + "@langchain/langgraph": ~0.0.26 "@langchain/openai": ">=0.2.0 <0.3.0" "@langchain/scripts": ^0.0.23 "@langchain/standard-tests": 0.0.0 @@ -11349,7 +11349,7 @@ __metadata: "@google-cloud/storage": ^6.10.1 || ^7.7.0 "@gradientai/nodejs-sdk": ^1.2.0 "@huggingface/inference": ^2.6.4 - "@langchain/langgraph": ~0.0.26 + "@langchain/langgraph": "*" "@layerup/layerup-security": ^1.5.12 "@mendable/firecrawl-js": ^0.0.13 "@mlc-ai/web-llm": 0.2.46 @@ -12010,11 +12010,11 @@ __metadata: languageName: unknown linkType: soft -"@langchain/langgraph@npm:*": - version: 0.0.34 - resolution: "@langchain/langgraph@npm:0.0.34" +"@langchain/langgraph@npm:0.0.28, @langchain/langgraph@npm:^0.0.28": + version: 0.0.28 + resolution: "@langchain/langgraph@npm:0.0.28" dependencies: - "@langchain/core": ">=0.2.20 <0.3.0" + "@langchain/core": ">=0.2.16 <0.3.0" uuid: ^10.0.0 zod: ^3.23.8 peerDependencies: @@ -12022,15 +12022,15 @@ __metadata: peerDependenciesMeta: better-sqlite3: optional: true - checksum: efa3c6b4eb40fdeb696531fe63f1f8e2b5d23b7adfc9404103cc9265db4aab6caee7de9ba1e01e14f2765d98a8b536bfbc473795543b0314e84c72b3b349a258 + checksum: 1465791026ccd6eaa13a2f2d03b8fb9f0972a8c23b9da1cfd581074f413ea60ef860de6d704c6a3b49f7425f23d6ba49c23255167ae83ab7d70dc00cc0560ce2 languageName: node linkType: hard -"@langchain/langgraph@npm:0.0.28, @langchain/langgraph@npm:^0.0.28": - version: 0.0.28 - resolution: "@langchain/langgraph@npm:0.0.28" +"@langchain/langgraph@npm:~0.0.26": + version: 0.0.34 + resolution: "@langchain/langgraph@npm:0.0.34" dependencies: - "@langchain/core": ">=0.2.16 <0.3.0" + "@langchain/core": ">=0.2.20 <0.3.0" uuid: ^10.0.0 zod: ^3.23.8 peerDependencies: @@ -12038,7 +12038,7 @@ __metadata: peerDependenciesMeta: better-sqlite3: optional: true - checksum: 1465791026ccd6eaa13a2f2d03b8fb9f0972a8c23b9da1cfd581074f413ea60ef860de6d704c6a3b49f7425f23d6ba49c23255167ae83ab7d70dc00cc0560ce2 + checksum: efa3c6b4eb40fdeb696531fe63f1f8e2b5d23b7adfc9404103cc9265db4aab6caee7de9ba1e01e14f2765d98a8b536bfbc473795543b0314e84c72b3b349a258 languageName: node linkType: hard From d10b45626755cb81e70859e040f9151dbe0f6243 Mon Sep 17 00:00:00 2001 From: Brace Sproul Date: Thu, 22 Aug 2024 14:02:05 -0700 Subject: [PATCH 077/344] create-langchain-integration[patch]: Release 0.0.11 (#6600) --- libs/create-langchain-integration/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libs/create-langchain-integration/package.json b/libs/create-langchain-integration/package.json index c8b3e57b2458..0bc7d5f23539 100644 --- a/libs/create-langchain-integration/package.json +++ b/libs/create-langchain-integration/package.json @@ -1,6 +1,6 @@ { "name": "create-langchain-integration", - "version": "0.0.10", + "version": "0.0.11", "repository": { "type": "git", "url": "https://github.com/langchain-ai/langchainjs", From f1d7c55eda0ef73b6e2426db03b6d6d2a9739924 Mon Sep 17 00:00:00 2001 From: Jacob Lee Date: Thu, 22 Aug 2024 14:13:10 -0700 Subject: [PATCH 078/344] community[patch]: Release 0.2.30 (#6601) --- libs/langchain-community/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libs/langchain-community/package.json b/libs/langchain-community/package.json index e8f5d3f2d2f6..3bb63b35a7ee 100644 --- a/libs/langchain-community/package.json +++ b/libs/langchain-community/package.json @@ -1,6 +1,6 @@ { "name": "@langchain/community", - "version": "0.2.29", + "version": "0.2.30", "description": "Third-party integrations for LangChain.js", "type": "module", "engines": { From b1a17daea461a9e00c66e30115da0d515ec081be Mon Sep 17 00:00:00 2001 From: Jack Reed Date: Thu, 22 Aug 2024 16:24:15 -0600 Subject: [PATCH 079/344] community[minor]: Use usage metadata message field for token reporting in DatadogLLMObsTracer (#6552) Co-authored-by: Brace Sproul --- .../src/experimental/callbacks/handlers/datadog.ts | 13 +++++++------ .../callbacks/handlers/tests/datadog.test.ts | 2 +- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/libs/langchain-community/src/experimental/callbacks/handlers/datadog.ts b/libs/langchain-community/src/experimental/callbacks/handlers/datadog.ts index 57a2e16daf3b..41dcf370fa12 100644 --- a/libs/langchain-community/src/experimental/callbacks/handlers/datadog.ts +++ b/libs/langchain-community/src/experimental/callbacks/handlers/datadog.ts @@ -2,7 +2,7 @@ import { BaseCallbackHandlerInput } from "@langchain/core/callbacks/base"; import { BaseTracer, Run } from "@langchain/core/tracers/base"; import { getEnvironmentVariable } from "@langchain/core/utils/env"; import { Document } from "@langchain/core/documents"; -import { BaseMessage } from "@langchain/core/messages"; +import { BaseMessage, isAIMessage } from "@langchain/core/messages"; import { ChatGeneration } from "@langchain/core/outputs"; import { KVMap } from "langsmith/schemas"; @@ -262,12 +262,13 @@ export class DatadogLLMObsTracer messages: outputs?.generations?.flatMap( (generations: ChatGeneration[]) => generations.map(({ message, text }) => { - const tokenUsage = message?.response_metadata?.tokenUsage; - if (tokenUsage) { + if (isAIMessage(message) && message?.usage_metadata) { + tokensMetadata.prompt_tokens = + message.usage_metadata.input_tokens; tokensMetadata.completion_tokens = - tokenUsage.completionTokens; - tokensMetadata.prompt_tokens = tokenUsage.promptTokens; - tokensMetadata.total_tokens = tokenUsage.totalTokens; + message.usage_metadata.output_tokens; + tokensMetadata.total_tokens = + message.usage_metadata.total_tokens; } return { diff --git a/libs/langchain-community/src/experimental/callbacks/handlers/tests/datadog.test.ts b/libs/langchain-community/src/experimental/callbacks/handlers/tests/datadog.test.ts index 0f5a015a0e81..b1db38c236bd 100644 --- a/libs/langchain-community/src/experimental/callbacks/handlers/tests/datadog.test.ts +++ b/libs/langchain-community/src/experimental/callbacks/handlers/tests/datadog.test.ts @@ -161,7 +161,7 @@ test("Test llm span with prompt input", async () => { generations: [ [ { - text: "Hi", + message: new AIMessage("Hi"), }, ], ], From b73c54205024f58db29f21465b590c643aa5278e Mon Sep 17 00:00:00 2001 From: "marsal.sans" Date: Fri, 23 Aug 2024 00:24:32 +0200 Subject: [PATCH 080/344] weaviate[minor]: Add Document id in similarity search results (#6576) Co-authored-by: Brace Sproul --- libs/langchain-weaviate/src/vectorstores.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/libs/langchain-weaviate/src/vectorstores.ts b/libs/langchain-weaviate/src/vectorstores.ts index e5604a5f90dc..4c6de1a3b723 100644 --- a/libs/langchain-weaviate/src/vectorstores.ts +++ b/libs/langchain-weaviate/src/vectorstores.ts @@ -295,7 +295,7 @@ export class WeaviateStore extends VectorStore { .get() .withClassName(this.indexName) .withFields( - `${this.queryAttrs.join(" ")} _additional { distance vector }` + `${this.queryAttrs.join(" ")} _additional { distance vector id }` ) .withNearVector({ vector: query, @@ -321,6 +321,7 @@ export class WeaviateStore extends VectorStore { new Document({ pageContent: text, metadata: rest, + id: _additional.id, }), _additional.distance, _additional.vector, From ce3f1aca72e3e74aeb5da605895e921867d1829e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vacant=E4=B8=B6?= <49608830+Versirity@users.noreply.github.com> Date: Fri, 23 Aug 2024 06:24:41 +0800 Subject: [PATCH 081/344] baidu-qianfan[patch]: Fix the issue of missing request parameters (#6575) Co-authored-by: wangweiming1 Co-authored-by: Brace Sproul --- libs/langchain-baidu-qianfan/src/chat_models.ts | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/libs/langchain-baidu-qianfan/src/chat_models.ts b/libs/langchain-baidu-qianfan/src/chat_models.ts index e1f0a3469f3c..6fc98ff48ce5 100644 --- a/libs/langchain-baidu-qianfan/src/chat_models.ts +++ b/libs/langchain-baidu-qianfan/src/chat_models.ts @@ -403,13 +403,7 @@ export class ChatBaiduQianfan ) { const makeCompletionRequest = async () => { console.log(request); - const response = await this.client.chat( - { - messages: request.messages, - stream, - }, - this.model - ); + const response = await this.client.chat(request, this.model); if (!stream) { return response; @@ -455,7 +449,7 @@ export class ChatBaiduQianfan private async createStream(request: ChatCompletionRequest) { const response = await this.client.chat( { - messages: request.messages, + ...request, stream: true, }, this.model From 6210960f0e09845e3a5458b004a3852b796b9630 Mon Sep 17 00:00:00 2001 From: Brace Sproul Date: Thu, 22 Aug 2024 15:27:27 -0700 Subject: [PATCH 082/344] baidu-qianfan[patch]: Release 0.0.3 (#6603) --- libs/langchain-baidu-qianfan/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libs/langchain-baidu-qianfan/package.json b/libs/langchain-baidu-qianfan/package.json index 84cf871e7e09..efa67706a7de 100644 --- a/libs/langchain-baidu-qianfan/package.json +++ b/libs/langchain-baidu-qianfan/package.json @@ -1,6 +1,6 @@ { "name": "@langchain/baidu-qianfan", - "version": "0.0.2", + "version": "0.0.3", "description": "Baidu Qianfan integration for LangChain.js", "type": "module", "engines": { From eeb82ba03174f73caa7e41dec341cecb4a27e36f Mon Sep 17 00:00:00 2001 From: Brace Sproul Date: Thu, 22 Aug 2024 15:31:00 -0700 Subject: [PATCH 083/344] weaviate[patch]: Release 0.0.5 (#6604) --- libs/langchain-weaviate/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libs/langchain-weaviate/package.json b/libs/langchain-weaviate/package.json index c29d97674172..b6b256a25b69 100644 --- a/libs/langchain-weaviate/package.json +++ b/libs/langchain-weaviate/package.json @@ -1,6 +1,6 @@ { "name": "@langchain/weaviate", - "version": "0.0.4", + "version": "0.0.5", "description": "Weaviate integration for LangChain.js", "type": "module", "engines": { From bf2e2b7f7ad7dbc40a93f96ec78ee42f6c954e21 Mon Sep 17 00:00:00 2001 From: Brace Sproul Date: Thu, 22 Aug 2024 15:34:39 -0700 Subject: [PATCH 084/344] scripts[patch]: Add notebook check for unexpected rebuild error (#6602) --- .../bin/validate_notebook.js | 2 +- .../check_notebook_type_errors.ts} | 8 +----- .../check_unexpected_rebuild_timer.ts | 26 +++++++++++++++++++ libs/langchain-scripts/src/notebooks/index.ts | 12 +++++++++ 4 files changed, 40 insertions(+), 8 deletions(-) rename libs/langchain-scripts/src/{validate_notebook.ts => notebooks/check_notebook_type_errors.ts} (98%) create mode 100644 libs/langchain-scripts/src/notebooks/check_unexpected_rebuild_timer.ts create mode 100644 libs/langchain-scripts/src/notebooks/index.ts diff --git a/libs/langchain-scripts/bin/validate_notebook.js b/libs/langchain-scripts/bin/validate_notebook.js index 2ff6aa968a34..cac5f69d91e8 100755 --- a/libs/langchain-scripts/bin/validate_notebook.js +++ b/libs/langchain-scripts/bin/validate_notebook.js @@ -1 +1 @@ -import "../dist/validate_notebook.js"; +import "../dist/notebooks/index.js"; diff --git a/libs/langchain-scripts/src/validate_notebook.ts b/libs/langchain-scripts/src/notebooks/check_notebook_type_errors.ts similarity index 98% rename from libs/langchain-scripts/src/validate_notebook.ts rename to libs/langchain-scripts/src/notebooks/check_notebook_type_errors.ts index 65907c159566..358f2aaa10eb 100644 --- a/libs/langchain-scripts/src/validate_notebook.ts +++ b/libs/langchain-scripts/src/notebooks/check_notebook_type_errors.ts @@ -69,7 +69,7 @@ if (!pathname) { throw new Error("No pathname provided."); } -const run = async () => { +export async function checkNotebookTypeErrors() { if (!pathname.endsWith(".ipynb")) { throw new Error("Only .ipynb files are supported."); } @@ -130,10 +130,4 @@ const run = async () => { // Do nothing } } -}; - -try { - void run(); -} catch { - process.exit(1); } diff --git a/libs/langchain-scripts/src/notebooks/check_unexpected_rebuild_timer.ts b/libs/langchain-scripts/src/notebooks/check_unexpected_rebuild_timer.ts new file mode 100644 index 000000000000..19cc3c4afcc2 --- /dev/null +++ b/libs/langchain-scripts/src/notebooks/check_unexpected_rebuild_timer.ts @@ -0,0 +1,26 @@ +import fs from "fs"; + +const [pathname] = process.argv.slice(2); + +if (!pathname) { + throw new Error("No pathname provided."); +} + +/** + * tslab will sometimes throw an error inside the output cells of a notebook + * if the notebook is being rebuilt. This function checks for that error, + * because we do not want to commit that to our docs. + */ +export async function checkUnexpectedRebuildError() { + if (!pathname.endsWith(".ipynb")) { + throw new Error("Only .ipynb files are supported."); + } + const notebookContents = await fs.promises.readFile(pathname, "utf-8"); + if ( + notebookContents.includes( + "UncaughtException: Error: Unexpected pending rebuildTimer" + ) + ) { + throw new Error(`Found unexpected pending rebuildTimer in ${pathname}`); + } +} diff --git a/libs/langchain-scripts/src/notebooks/index.ts b/libs/langchain-scripts/src/notebooks/index.ts new file mode 100644 index 000000000000..3eccf3906de2 --- /dev/null +++ b/libs/langchain-scripts/src/notebooks/index.ts @@ -0,0 +1,12 @@ +import { checkNotebookTypeErrors } from "./check_notebook_type_errors.js"; +import { checkUnexpectedRebuildError } from "./check_unexpected_rebuild_timer.js"; + +async function main() { + await Promise.all([checkNotebookTypeErrors(), checkUnexpectedRebuildError()]); +} + +try { + void main(); +} catch { + process.exit(1); +} From afaa0f6cf6390fbbf065fcd45637e73dcd8dca16 Mon Sep 17 00:00:00 2001 From: Brace Sproul Date: Thu, 22 Aug 2024 15:36:33 -0700 Subject: [PATCH 085/344] scripts[minor]: Release 0.1.0 (#6605) --- libs/langchain-scripts/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libs/langchain-scripts/package.json b/libs/langchain-scripts/package.json index db77d0a36d68..7b5758284a9b 100644 --- a/libs/langchain-scripts/package.json +++ b/libs/langchain-scripts/package.json @@ -1,6 +1,6 @@ { "name": "@langchain/scripts", - "version": "0.0.23", + "version": "0.1.0", "description": "Shared scripts for LangChain.js", "type": "module", "engines": { From 903ff79b6293453ad567bdfeca3887f0a86aead9 Mon Sep 17 00:00:00 2001 From: Brace Sproul Date: Thu, 22 Aug 2024 15:58:43 -0700 Subject: [PATCH 086/344] all[patch]: Bump scripts version (#6606) --- examples/package.json | 2 +- langchain-core/package.json | 2 +- langchain/package.json | 2 +- .../template/package.json | 2 +- libs/langchain-anthropic/package.json | 2 +- libs/langchain-aws/package.json | 2 +- libs/langchain-azure-cosmosdb/package.json | 2 +- .../package.json | 2 +- libs/langchain-azure-openai/package.json | 2 +- libs/langchain-baidu-qianfan/package.json | 2 +- libs/langchain-cloudflare/package.json | 2 +- libs/langchain-cohere/package.json | 2 +- libs/langchain-community/package.json | 2 +- libs/langchain-exa/package.json | 2 +- libs/langchain-google-common/package.json | 2 +- libs/langchain-google-gauth/package.json | 2 +- libs/langchain-google-genai/package.json | 2 +- .../package.json | 2 +- libs/langchain-google-vertexai/package.json | 2 +- libs/langchain-google-webauth/package.json | 2 +- libs/langchain-groq/package.json | 2 +- libs/langchain-mistralai/package.json | 2 +- libs/langchain-mixedbread-ai/package.json | 2 +- libs/langchain-mongodb/package.json | 2 +- libs/langchain-nomic/package.json | 2 +- libs/langchain-ollama/package.json | 2 +- libs/langchain-openai/package.json | 2 +- libs/langchain-pinecone/package.json | 2 +- libs/langchain-qdrant/package.json | 2 +- libs/langchain-redis/package.json | 2 +- libs/langchain-textsplitters/package.json | 2 +- libs/langchain-weaviate/package.json | 2 +- libs/langchain-yandex/package.json | 2 +- yarn.lock | 66 +++++++++---------- 34 files changed, 66 insertions(+), 66 deletions(-) diff --git a/examples/package.json b/examples/package.json index 340405e6b8ad..c3dab06a5b36 100644 --- a/examples/package.json +++ b/examples/package.json @@ -58,7 +58,7 @@ "@langchain/pinecone": "workspace:*", "@langchain/qdrant": "workspace:*", "@langchain/redis": "workspace:*", - "@langchain/scripts": "^0.0.23", + "@langchain/scripts": ">=0.1.0 <0.2.0", "@langchain/textsplitters": "workspace:*", "@langchain/weaviate": "workspace:*", "@langchain/yandex": "workspace:*", diff --git a/langchain-core/package.json b/langchain-core/package.json index 1307d14027b3..0f1ee3b0bbcb 100644 --- a/langchain-core/package.json +++ b/langchain-core/package.json @@ -47,7 +47,7 @@ }, "devDependencies": { "@jest/globals": "^29.5.0", - "@langchain/scripts": "^0.0.23", + "@langchain/scripts": ">=0.1.0 <0.2.0", "@swc/core": "^1.3.90", "@swc/jest": "^0.2.29", "@types/decamelize": "^1.2.0", diff --git a/langchain/package.json b/langchain/package.json index 18aa16d1c363..5c83ce64f9ee 100644 --- a/langchain/package.json +++ b/langchain/package.json @@ -613,7 +613,7 @@ "@langchain/groq": "^0.0.15", "@langchain/mistralai": "^0.0.26", "@langchain/ollama": "^0.0.2", - "@langchain/scripts": "^0.0.23", + "@langchain/scripts": ">=0.1.0 <0.2.0", "@mendable/firecrawl-js": "^0.0.13", "@notionhq/client": "^2.2.10", "@pinecone-database/pinecone": "^1.1.0", diff --git a/libs/create-langchain-integration/template/package.json b/libs/create-langchain-integration/template/package.json index dea7417b214a..2f2cc09dda3e 100644 --- a/libs/create-langchain-integration/template/package.json +++ b/libs/create-langchain-integration/template/package.json @@ -38,7 +38,7 @@ "@jest/globals": "^29.5.0", "@swc/core": "^1.3.90", "@swc/jest": "^0.2.29", - "@langchain/scripts": "^0.0.23", + "@langchain/scripts": ">=0.1.0 <0.2.0", "@tsconfig/recommended": "^1.0.3", "@typescript-eslint/eslint-plugin": "^6.12.0", "@typescript-eslint/parser": "^6.12.0", diff --git a/libs/langchain-anthropic/package.json b/libs/langchain-anthropic/package.json index 550bb654c321..fe8027f36ffb 100644 --- a/libs/langchain-anthropic/package.json +++ b/libs/langchain-anthropic/package.json @@ -43,7 +43,7 @@ }, "devDependencies": { "@jest/globals": "^29.5.0", - "@langchain/scripts": "^0.0.23", + "@langchain/scripts": ">=0.1.0 <0.2.0", "@langchain/standard-tests": "0.0.0", "@swc/core": "^1.3.90", "@swc/jest": "^0.2.29", diff --git a/libs/langchain-aws/package.json b/libs/langchain-aws/package.json index a4a18e716fa9..40fe56eff4d7 100644 --- a/libs/langchain-aws/package.json +++ b/libs/langchain-aws/package.json @@ -43,7 +43,7 @@ "devDependencies": { "@aws-sdk/types": "^3.609.0", "@jest/globals": "^29.5.0", - "@langchain/scripts": "^0.0.23", + "@langchain/scripts": ">=0.1.0 <0.2.0", "@langchain/standard-tests": "0.0.0", "@smithy/types": "^3.2.0", "@swc/core": "^1.3.90", diff --git a/libs/langchain-azure-cosmosdb/package.json b/libs/langchain-azure-cosmosdb/package.json index 6a8a02adebb1..161ac0b3c8b4 100644 --- a/libs/langchain-azure-cosmosdb/package.json +++ b/libs/langchain-azure-cosmosdb/package.json @@ -40,7 +40,7 @@ "devDependencies": { "@jest/globals": "^29.5.0", "@langchain/openai": "workspace:^", - "@langchain/scripts": "~0.0", + "@langchain/scripts": ">=0.1.0 <0.2.0", "@swc/core": "^1.3.90", "@swc/jest": "^0.2.29", "@tsconfig/recommended": "^1.0.3", diff --git a/libs/langchain-azure-dynamic-sessions/package.json b/libs/langchain-azure-dynamic-sessions/package.json index 2c8e0ef7e80c..ea1101079811 100644 --- a/libs/langchain-azure-dynamic-sessions/package.json +++ b/libs/langchain-azure-dynamic-sessions/package.json @@ -38,7 +38,7 @@ }, "devDependencies": { "@jest/globals": "^29.5.0", - "@langchain/scripts": "^0.0.23", + "@langchain/scripts": ">=0.1.0 <0.2.0", "@swc/core": "^1.3.90", "@swc/jest": "^0.2.29", "@tsconfig/recommended": "^1.0.3", diff --git a/libs/langchain-azure-openai/package.json b/libs/langchain-azure-openai/package.json index 7d342c60ec33..c5351845caeb 100644 --- a/libs/langchain-azure-openai/package.json +++ b/libs/langchain-azure-openai/package.json @@ -41,7 +41,7 @@ "devDependencies": { "@azure/identity": "^4.2.1", "@jest/globals": "^29.5.0", - "@langchain/scripts": "^0.0.23", + "@langchain/scripts": ">=0.1.0 <0.2.0", "@langchain/standard-tests": "0.0.0", "@swc/core": "^1.3.90", "@swc/jest": "^0.2.29", diff --git a/libs/langchain-baidu-qianfan/package.json b/libs/langchain-baidu-qianfan/package.json index efa67706a7de..e6648d52a569 100644 --- a/libs/langchain-baidu-qianfan/package.json +++ b/libs/langchain-baidu-qianfan/package.json @@ -40,7 +40,7 @@ "devDependencies": { "@jest/globals": "^29.5.0", "@langchain/openai": "~0.1.0", - "@langchain/scripts": "^0.0.23", + "@langchain/scripts": ">=0.1.0 <0.2.0", "@swc/core": "^1.3.90", "@swc/jest": "^0.2.29", "@tsconfig/recommended": "^1.0.3", diff --git a/libs/langchain-cloudflare/package.json b/libs/langchain-cloudflare/package.json index 171bd43a0fdc..d6c9a84ca0d1 100644 --- a/libs/langchain-cloudflare/package.json +++ b/libs/langchain-cloudflare/package.json @@ -43,7 +43,7 @@ "@cloudflare/workers-types": "^4.20231218.0", "@jest/globals": "^29.5.0", "@langchain/langgraph": "~0.0.31", - "@langchain/scripts": "^0.0.23", + "@langchain/scripts": ">=0.1.0 <0.2.0", "@langchain/standard-tests": "0.0.0", "@swc/core": "^1.3.90", "@swc/jest": "^0.2.29", diff --git a/libs/langchain-cohere/package.json b/libs/langchain-cohere/package.json index eeb1035c0fde..a140b8cbfc1e 100644 --- a/libs/langchain-cohere/package.json +++ b/libs/langchain-cohere/package.json @@ -43,7 +43,7 @@ }, "devDependencies": { "@jest/globals": "^29.5.0", - "@langchain/scripts": "^0.0.23", + "@langchain/scripts": ">=0.1.0 <0.2.0", "@langchain/standard-tests": "0.0.0", "@swc/core": "^1.3.90", "@swc/jest": "^0.2.29", diff --git a/libs/langchain-community/package.json b/libs/langchain-community/package.json index 3bb63b35a7ee..2f3767be39ac 100644 --- a/libs/langchain-community/package.json +++ b/libs/langchain-community/package.json @@ -79,7 +79,7 @@ "@huggingface/inference": "^2.6.4", "@jest/globals": "^29.5.0", "@langchain/langgraph": "~0.0.26", - "@langchain/scripts": "^0.0.23", + "@langchain/scripts": ">=0.1.0 <0.2.0", "@langchain/standard-tests": "0.0.0", "@layerup/layerup-security": "^1.5.12", "@mendable/firecrawl-js": "^0.0.36", diff --git a/libs/langchain-exa/package.json b/libs/langchain-exa/package.json index 20b313377c36..ebbb59a167f8 100644 --- a/libs/langchain-exa/package.json +++ b/libs/langchain-exa/package.json @@ -37,7 +37,7 @@ }, "devDependencies": { "@jest/globals": "^29.5.0", - "@langchain/scripts": "^0.0.23", + "@langchain/scripts": ">=0.1.0 <0.2.0", "@swc/core": "^1.3.90", "@swc/jest": "^0.2.29", "@tsconfig/recommended": "^1.0.3", diff --git a/libs/langchain-google-common/package.json b/libs/langchain-google-common/package.json index 634c32d400e7..c913b36f38bd 100644 --- a/libs/langchain-google-common/package.json +++ b/libs/langchain-google-common/package.json @@ -38,7 +38,7 @@ }, "devDependencies": { "@jest/globals": "^29.5.0", - "@langchain/scripts": "^0.0.23", + "@langchain/scripts": ">=0.1.0 <0.2.0", "@swc/core": "^1.3.90", "@swc/jest": "^0.2.29", "@tsconfig/recommended": "^1.0.3", diff --git a/libs/langchain-google-gauth/package.json b/libs/langchain-google-gauth/package.json index f6821c67f17c..3924cb8b97b2 100644 --- a/libs/langchain-google-gauth/package.json +++ b/libs/langchain-google-gauth/package.json @@ -41,7 +41,7 @@ }, "devDependencies": { "@jest/globals": "^29.5.0", - "@langchain/scripts": "^0.0.23", + "@langchain/scripts": ">=0.1.0 <0.2.0", "@swc/core": "^1.3.90", "@swc/jest": "^0.2.29", "@tsconfig/recommended": "^1.0.3", diff --git a/libs/langchain-google-genai/package.json b/libs/langchain-google-genai/package.json index d6792fd43c07..e0b64b7114c9 100644 --- a/libs/langchain-google-genai/package.json +++ b/libs/langchain-google-genai/package.json @@ -41,7 +41,7 @@ }, "devDependencies": { "@jest/globals": "^29.5.0", - "@langchain/scripts": "^0.0.23", + "@langchain/scripts": ">=0.1.0 <0.2.0", "@langchain/standard-tests": "0.0.0", "@swc/core": "^1.3.90", "@swc/jest": "^0.2.29", diff --git a/libs/langchain-google-vertexai-web/package.json b/libs/langchain-google-vertexai-web/package.json index 26341f930fa3..105600328c59 100644 --- a/libs/langchain-google-vertexai-web/package.json +++ b/libs/langchain-google-vertexai-web/package.json @@ -38,7 +38,7 @@ "devDependencies": { "@jest/globals": "^29.5.0", "@langchain/google-common": "~0.0", - "@langchain/scripts": "^0.0.23", + "@langchain/scripts": ">=0.1.0 <0.2.0", "@langchain/standard-tests": "0.0.0", "@swc/core": "^1.3.90", "@swc/jest": "^0.2.29", diff --git a/libs/langchain-google-vertexai/package.json b/libs/langchain-google-vertexai/package.json index 176d427f54fe..143e7505ce17 100644 --- a/libs/langchain-google-vertexai/package.json +++ b/libs/langchain-google-vertexai/package.json @@ -38,7 +38,7 @@ "devDependencies": { "@jest/globals": "^29.5.0", "@langchain/google-common": "~0.0", - "@langchain/scripts": "^0.0.23", + "@langchain/scripts": ">=0.1.0 <0.2.0", "@langchain/standard-tests": "0.0.0", "@swc/core": "^1.3.90", "@swc/jest": "^0.2.29", diff --git a/libs/langchain-google-webauth/package.json b/libs/langchain-google-webauth/package.json index efdd9a744a48..3ed28832fea2 100644 --- a/libs/langchain-google-webauth/package.json +++ b/libs/langchain-google-webauth/package.json @@ -38,7 +38,7 @@ }, "devDependencies": { "@jest/globals": "^29.5.0", - "@langchain/scripts": "^0.0.23", + "@langchain/scripts": ">=0.1.0 <0.2.0", "@swc/core": "^1.3.90", "@swc/jest": "^0.2.29", "@tsconfig/recommended": "^1.0.3", diff --git a/libs/langchain-groq/package.json b/libs/langchain-groq/package.json index 616bebcf750b..bb816bc376cd 100644 --- a/libs/langchain-groq/package.json +++ b/libs/langchain-groq/package.json @@ -44,7 +44,7 @@ "devDependencies": { "@jest/globals": "^29.5.0", "@langchain/openai": "workspace:^", - "@langchain/scripts": "^0.0.23", + "@langchain/scripts": ">=0.1.0 <0.2.0", "@langchain/standard-tests": "0.0.0", "@swc/core": "^1.3.90", "@swc/jest": "^0.2.29", diff --git a/libs/langchain-mistralai/package.json b/libs/langchain-mistralai/package.json index c757c40b9750..5e1b587975f8 100644 --- a/libs/langchain-mistralai/package.json +++ b/libs/langchain-mistralai/package.json @@ -43,7 +43,7 @@ }, "devDependencies": { "@jest/globals": "^29.5.0", - "@langchain/scripts": "^0.0.23", + "@langchain/scripts": ">=0.1.0 <0.2.0", "@langchain/standard-tests": "0.0.0", "@swc/core": "^1.3.90", "@swc/jest": "^0.2.29", diff --git a/libs/langchain-mixedbread-ai/package.json b/libs/langchain-mixedbread-ai/package.json index 4a9015891868..9d6e6a5d7e50 100644 --- a/libs/langchain-mixedbread-ai/package.json +++ b/libs/langchain-mixedbread-ai/package.json @@ -40,7 +40,7 @@ }, "devDependencies": { "@jest/globals": "^29.5.0", - "@langchain/scripts": "^0.0.23", + "@langchain/scripts": ">=0.1.0 <0.2.0", "@langchain/standard-tests": "0.0.0", "@swc/core": "^1.3.90", "@swc/jest": "^0.2.29", diff --git a/libs/langchain-mongodb/package.json b/libs/langchain-mongodb/package.json index 9b6b1557e330..0cca4d7d6006 100644 --- a/libs/langchain-mongodb/package.json +++ b/libs/langchain-mongodb/package.json @@ -38,7 +38,7 @@ "devDependencies": { "@jest/globals": "^29.5.0", "@langchain/openai": "workspace:*", - "@langchain/scripts": "^0.0.23", + "@langchain/scripts": ">=0.1.0 <0.2.0", "@swc/core": "^1.3.90", "@swc/jest": "^0.2.29", "@tsconfig/recommended": "^1.0.3", diff --git a/libs/langchain-nomic/package.json b/libs/langchain-nomic/package.json index 0c676b062876..8ea6f4aa793f 100644 --- a/libs/langchain-nomic/package.json +++ b/libs/langchain-nomic/package.json @@ -38,7 +38,7 @@ "devDependencies": { "@jest/globals": "^29.5.0", "@langchain/openai": "workspace:^", - "@langchain/scripts": "^0.0.23", + "@langchain/scripts": ">=0.1.0 <0.2.0", "@swc/core": "^1.3.90", "@swc/jest": "^0.2.29", "@tsconfig/recommended": "^1.0.3", diff --git a/libs/langchain-ollama/package.json b/libs/langchain-ollama/package.json index c18f33538808..5aa3ccf94bf2 100644 --- a/libs/langchain-ollama/package.json +++ b/libs/langchain-ollama/package.json @@ -38,7 +38,7 @@ }, "devDependencies": { "@jest/globals": "^29.5.0", - "@langchain/scripts": "^0.0.23", + "@langchain/scripts": ">=0.1.0 <0.2.0", "@langchain/standard-tests": "0.0.0", "@swc/core": "^1.3.90", "@swc/jest": "^0.2.29", diff --git a/libs/langchain-openai/package.json b/libs/langchain-openai/package.json index 831946ffc1fd..b6fd72c247cc 100644 --- a/libs/langchain-openai/package.json +++ b/libs/langchain-openai/package.json @@ -44,7 +44,7 @@ "devDependencies": { "@azure/identity": "^4.2.1", "@jest/globals": "^29.5.0", - "@langchain/scripts": "^0.0.23", + "@langchain/scripts": ">=0.1.0 <0.2.0", "@langchain/standard-tests": "0.0.0", "@swc/core": "^1.3.90", "@swc/jest": "^0.2.29", diff --git a/libs/langchain-pinecone/package.json b/libs/langchain-pinecone/package.json index eed6620abc5f..bf5e64044655 100644 --- a/libs/langchain-pinecone/package.json +++ b/libs/langchain-pinecone/package.json @@ -41,7 +41,7 @@ "@faker-js/faker": "^8.3.1", "@jest/globals": "^29.5.0", "@langchain/openai": "workspace:*", - "@langchain/scripts": "^0.0.23", + "@langchain/scripts": ">=0.1.0 <0.2.0", "@swc/core": "^1.3.90", "@swc/jest": "^0.2.29", "@tsconfig/recommended": "^1.0.3", diff --git a/libs/langchain-qdrant/package.json b/libs/langchain-qdrant/package.json index b67aaa635d4c..c477b34431f9 100644 --- a/libs/langchain-qdrant/package.json +++ b/libs/langchain-qdrant/package.json @@ -39,7 +39,7 @@ "devDependencies": { "@faker-js/faker": "^8.4.1", "@jest/globals": "^29.5.0", - "@langchain/scripts": "^0.0.23", + "@langchain/scripts": ">=0.1.0 <0.2.0", "@swc/core": "^1.3.90", "@swc/jest": "^0.2.29", "@tsconfig/recommended": "^1.0.3", diff --git a/libs/langchain-redis/package.json b/libs/langchain-redis/package.json index d321f4e47a42..1dab09ed191c 100644 --- a/libs/langchain-redis/package.json +++ b/libs/langchain-redis/package.json @@ -38,7 +38,7 @@ "devDependencies": { "@faker-js/faker": "^8.4.0", "@jest/globals": "^29.5.0", - "@langchain/scripts": "^0.0.23", + "@langchain/scripts": ">=0.1.0 <0.2.0", "@swc/core": "^1.3.90", "@swc/jest": "^0.2.29", "@tsconfig/recommended": "^1.0.3", diff --git a/libs/langchain-textsplitters/package.json b/libs/langchain-textsplitters/package.json index 67bfea71f685..7948be974102 100644 --- a/libs/langchain-textsplitters/package.json +++ b/libs/langchain-textsplitters/package.json @@ -37,7 +37,7 @@ }, "devDependencies": { "@jest/globals": "^29.5.0", - "@langchain/scripts": "^0.0.23", + "@langchain/scripts": ">=0.1.0 <0.2.0", "@swc/core": "^1.3.90", "@swc/jest": "^0.2.29", "@tsconfig/recommended": "^1.0.3", diff --git a/libs/langchain-weaviate/package.json b/libs/langchain-weaviate/package.json index b6b256a25b69..44fac83cbdd4 100644 --- a/libs/langchain-weaviate/package.json +++ b/libs/langchain-weaviate/package.json @@ -39,7 +39,7 @@ "devDependencies": { "@jest/globals": "^29.5.0", "@langchain/openai": "workspace:^", - "@langchain/scripts": "^0.0.23", + "@langchain/scripts": ">=0.1.0 <0.2.0", "@swc/core": "^1.3.90", "@swc/jest": "^0.2.29", "@tsconfig/recommended": "^1.0.3", diff --git a/libs/langchain-yandex/package.json b/libs/langchain-yandex/package.json index fce662e8c06d..c6ab4910b996 100644 --- a/libs/langchain-yandex/package.json +++ b/libs/langchain-yandex/package.json @@ -36,7 +36,7 @@ }, "devDependencies": { "@jest/globals": "^29.5.0", - "@langchain/scripts": "^0.0.23", + "@langchain/scripts": ">=0.1.0 <0.2.0", "@swc/core": "^1.3.90", "@swc/jest": "^0.2.29", "@tsconfig/recommended": "^1.0.3", diff --git a/yarn.lock b/yarn.lock index c37553989950..8a47d3436178 100644 --- a/yarn.lock +++ b/yarn.lock @@ -10854,7 +10854,7 @@ __metadata: "@anthropic-ai/sdk": ^0.25.2 "@jest/globals": ^29.5.0 "@langchain/core": ">=0.2.21 <0.3.0" - "@langchain/scripts": ^0.0.23 + "@langchain/scripts": ">=0.1.0 <0.2.0" "@langchain/standard-tests": 0.0.0 "@swc/core": ^1.3.90 "@swc/jest": ^0.2.29 @@ -10904,7 +10904,7 @@ __metadata: "@aws-sdk/types": ^3.609.0 "@jest/globals": ^29.5.0 "@langchain/core": ">=0.2.21 <0.3.0" - "@langchain/scripts": ^0.0.23 + "@langchain/scripts": ">=0.1.0 <0.2.0" "@langchain/standard-tests": 0.0.0 "@smithy/types": ^3.2.0 "@swc/core": ^1.3.90 @@ -10941,7 +10941,7 @@ __metadata: "@jest/globals": ^29.5.0 "@langchain/core": ~0.2 "@langchain/openai": "workspace:^" - "@langchain/scripts": ~0.0 + "@langchain/scripts": ">=0.1.0 <0.2.0" "@swc/core": ^1.3.90 "@swc/jest": ^0.2.29 "@tsconfig/recommended": ^1.0.3 @@ -10973,7 +10973,7 @@ __metadata: "@azure/identity": ^4.2.1 "@jest/globals": ^29.5.0 "@langchain/core": ~0.2 - "@langchain/scripts": ^0.0.23 + "@langchain/scripts": ">=0.1.0 <0.2.0" "@swc/core": ^1.3.90 "@swc/jest": ^0.2.29 "@tsconfig/recommended": ^1.0.3 @@ -11008,7 +11008,7 @@ __metadata: "@azure/openai": 1.0.0-beta.11 "@jest/globals": ^29.5.0 "@langchain/core": ">0.1.0 <0.3.0" - "@langchain/scripts": ^0.0.23 + "@langchain/scripts": ">=0.1.0 <0.2.0" "@langchain/standard-tests": 0.0.0 "@swc/core": ^1.3.90 "@swc/jest": ^0.2.29 @@ -11040,7 +11040,7 @@ __metadata: "@jest/globals": ^29.5.0 "@langchain/core": ">0.1.56 <0.3.0" "@langchain/openai": ~0.1.0 - "@langchain/scripts": ^0.0.23 + "@langchain/scripts": ">=0.1.0 <0.2.0" "@swc/core": ^1.3.90 "@swc/jest": ^0.2.29 "@tsconfig/recommended": ^1.0.3 @@ -11076,7 +11076,7 @@ __metadata: "@jest/globals": ^29.5.0 "@langchain/core": ">0.1.0 <0.3.0" "@langchain/langgraph": ~0.0.31 - "@langchain/scripts": ^0.0.23 + "@langchain/scripts": ">=0.1.0 <0.2.0" "@langchain/standard-tests": 0.0.0 "@swc/core": ^1.3.90 "@swc/jest": ^0.2.29 @@ -11114,7 +11114,7 @@ __metadata: dependencies: "@jest/globals": ^29.5.0 "@langchain/core": ">=0.2.21 <0.3.0" - "@langchain/scripts": ^0.0.23 + "@langchain/scripts": ">=0.1.0 <0.2.0" "@langchain/standard-tests": 0.0.0 "@swc/core": ^1.3.90 "@swc/jest": ^0.2.29 @@ -11181,7 +11181,7 @@ __metadata: "@langchain/core": ">=0.2.21 <0.3.0" "@langchain/langgraph": ~0.0.26 "@langchain/openai": ">=0.2.0 <0.3.0" - "@langchain/scripts": ^0.0.23 + "@langchain/scripts": ">=0.1.0 <0.2.0" "@langchain/standard-tests": 0.0.0 "@layerup/layerup-security": ^1.5.12 "@mendable/firecrawl-js": ^0.0.36 @@ -11692,7 +11692,7 @@ __metadata: resolution: "@langchain/core@workspace:langchain-core" dependencies: "@jest/globals": ^29.5.0 - "@langchain/scripts": ^0.0.23 + "@langchain/scripts": ">=0.1.0 <0.2.0" "@swc/core": ^1.3.90 "@swc/jest": ^0.2.29 "@types/decamelize": ^1.2.0 @@ -11734,7 +11734,7 @@ __metadata: dependencies: "@jest/globals": ^29.5.0 "@langchain/core": ">0.1.0 <0.3.0" - "@langchain/scripts": ^0.0.23 + "@langchain/scripts": ">=0.1.0 <0.2.0" "@swc/core": ^1.3.90 "@swc/jest": ^0.2.29 "@tsconfig/recommended": ^1.0.3 @@ -11766,7 +11766,7 @@ __metadata: dependencies: "@jest/globals": ^29.5.0 "@langchain/core": ">=0.2.21 <0.3.0" - "@langchain/scripts": ^0.0.23 + "@langchain/scripts": ">=0.1.0 <0.2.0" "@swc/core": ^1.3.90 "@swc/jest": ^0.2.29 "@tsconfig/recommended": ^1.0.3 @@ -11800,7 +11800,7 @@ __metadata: "@jest/globals": ^29.5.0 "@langchain/core": ">=0.2.21 <0.3.0" "@langchain/google-common": ~0.0.26 - "@langchain/scripts": ^0.0.23 + "@langchain/scripts": ">=0.1.0 <0.2.0" "@swc/core": ^1.3.90 "@swc/jest": ^0.2.29 "@tsconfig/recommended": ^1.0.3 @@ -11833,7 +11833,7 @@ __metadata: "@google/generative-ai": ^0.7.0 "@jest/globals": ^29.5.0 "@langchain/core": ">=0.2.21 <0.3.0" - "@langchain/scripts": ^0.0.23 + "@langchain/scripts": ">=0.1.0 <0.2.0" "@langchain/standard-tests": 0.0.0 "@swc/core": ^1.3.90 "@swc/jest": ^0.2.29 @@ -11869,7 +11869,7 @@ __metadata: "@langchain/core": ">=0.2.21 <0.3.0" "@langchain/google-common": ~0.0 "@langchain/google-webauth": ~0.0.26 - "@langchain/scripts": ^0.0.23 + "@langchain/scripts": ">=0.1.0 <0.2.0" "@langchain/standard-tests": 0.0.0 "@swc/core": ^1.3.90 "@swc/jest": ^0.2.29 @@ -11903,7 +11903,7 @@ __metadata: "@langchain/core": ">=0.2.21 <0.3.0" "@langchain/google-common": ~0.0 "@langchain/google-gauth": ~0.0.26 - "@langchain/scripts": ^0.0.23 + "@langchain/scripts": ">=0.1.0 <0.2.0" "@langchain/standard-tests": 0.0.0 "@swc/core": ^1.3.90 "@swc/jest": ^0.2.29 @@ -11936,7 +11936,7 @@ __metadata: "@jest/globals": ^29.5.0 "@langchain/core": ">=0.2.21 <0.3.0" "@langchain/google-common": ~0.0.26 - "@langchain/scripts": ^0.0.23 + "@langchain/scripts": ">=0.1.0 <0.2.0" "@swc/core": ^1.3.90 "@swc/jest": ^0.2.29 "@tsconfig/recommended": ^1.0.3 @@ -11981,7 +11981,7 @@ __metadata: "@jest/globals": ^29.5.0 "@langchain/core": ">=0.2.21 <0.3.0" "@langchain/openai": "workspace:^" - "@langchain/scripts": ^0.0.23 + "@langchain/scripts": ">=0.1.0 <0.2.0" "@langchain/standard-tests": 0.0.0 "@swc/core": ^1.3.90 "@swc/jest": ^0.2.29 @@ -12077,7 +12077,7 @@ __metadata: dependencies: "@jest/globals": ^29.5.0 "@langchain/core": ">=0.2.21 <0.3.0" - "@langchain/scripts": ^0.0.23 + "@langchain/scripts": ">=0.1.0 <0.2.0" "@langchain/standard-tests": 0.0.0 "@mistralai/mistralai": ^1.1.0 "@swc/core": ^1.3.90 @@ -12112,7 +12112,7 @@ __metadata: dependencies: "@jest/globals": ^29.5.0 "@langchain/core": ">=0.2.5 <0.3.0" - "@langchain/scripts": ^0.0.23 + "@langchain/scripts": ">=0.1.0 <0.2.0" "@langchain/standard-tests": 0.0.0 "@mixedbread-ai/sdk": ^2.2.3 "@swc/core": ^1.3.90 @@ -12146,7 +12146,7 @@ __metadata: "@jest/globals": ^29.5.0 "@langchain/core": ">0.1.0 <0.3.0" "@langchain/openai": "workspace:*" - "@langchain/scripts": ^0.0.23 + "@langchain/scripts": ">=0.1.0 <0.2.0" "@swc/core": ^1.3.90 "@swc/jest": ^0.2.29 "@tsconfig/recommended": ^1.0.3 @@ -12180,7 +12180,7 @@ __metadata: "@jest/globals": ^29.5.0 "@langchain/core": ">0.1.0 <0.3.0" "@langchain/openai": "workspace:^" - "@langchain/scripts": ^0.0.23 + "@langchain/scripts": ">=0.1.0 <0.2.0" "@nomic-ai/atlas": ^0.8.0 "@swc/core": ^1.3.90 "@swc/jest": ^0.2.29 @@ -12223,7 +12223,7 @@ __metadata: dependencies: "@jest/globals": ^29.5.0 "@langchain/core": ">=0.2.21 <0.3.0" - "@langchain/scripts": ^0.0.23 + "@langchain/scripts": ">=0.1.0 <0.2.0" "@langchain/standard-tests": 0.0.0 "@swc/core": ^1.3.90 "@swc/jest": ^0.2.29 @@ -12259,7 +12259,7 @@ __metadata: "@azure/identity": ^4.2.1 "@jest/globals": ^29.5.0 "@langchain/core": ">=0.2.26 <0.3.0" - "@langchain/scripts": ^0.0.23 + "@langchain/scripts": ">=0.1.0 <0.2.0" "@langchain/standard-tests": 0.0.0 "@swc/core": ^1.3.90 "@swc/jest": ^0.2.29 @@ -12306,7 +12306,7 @@ __metadata: "@jest/globals": ^29.5.0 "@langchain/core": ">0.2.0 <0.3.0" "@langchain/openai": "workspace:*" - "@langchain/scripts": ^0.0.23 + "@langchain/scripts": ">=0.1.0 <0.2.0" "@pinecone-database/pinecone": ^3.0.0 "@swc/core": ^1.3.90 "@swc/jest": ^0.2.29 @@ -12341,7 +12341,7 @@ __metadata: "@faker-js/faker": ^8.4.1 "@jest/globals": ^29.5.0 "@langchain/core": ">0.1.0 <0.3.0" - "@langchain/scripts": ^0.0.23 + "@langchain/scripts": ">=0.1.0 <0.2.0" "@qdrant/js-client-rest": ^1.9.0 "@swc/core": ^1.3.90 "@swc/jest": ^0.2.29 @@ -12375,7 +12375,7 @@ __metadata: "@faker-js/faker": ^8.4.0 "@jest/globals": ^29.5.0 "@langchain/core": ">0.1.0 <0.3.0" - "@langchain/scripts": ^0.0.23 + "@langchain/scripts": ">=0.1.0 <0.2.0" "@swc/core": ^1.3.90 "@swc/jest": ^0.2.29 "@tsconfig/recommended": ^1.0.3 @@ -12402,7 +12402,7 @@ __metadata: languageName: unknown linkType: soft -"@langchain/scripts@^0.0.23, @langchain/scripts@workspace:*, @langchain/scripts@workspace:libs/langchain-scripts, @langchain/scripts@~0.0": +"@langchain/scripts@>=0.1.0 <0.2.0, @langchain/scripts@workspace:*, @langchain/scripts@workspace:libs/langchain-scripts": version: 0.0.0-use.local resolution: "@langchain/scripts@workspace:libs/langchain-scripts" dependencies: @@ -12481,7 +12481,7 @@ __metadata: dependencies: "@jest/globals": ^29.5.0 "@langchain/core": ">0.2.0 <0.3.0" - "@langchain/scripts": ^0.0.23 + "@langchain/scripts": ">=0.1.0 <0.2.0" "@swc/core": ^1.3.90 "@swc/jest": ^0.2.29 "@tsconfig/recommended": ^1.0.3 @@ -12513,7 +12513,7 @@ __metadata: "@jest/globals": ^29.5.0 "@langchain/core": ">0.2.0 <0.3.0" "@langchain/openai": "workspace:^" - "@langchain/scripts": ^0.0.23 + "@langchain/scripts": ">=0.1.0 <0.2.0" "@swc/core": ^1.3.90 "@swc/jest": ^0.2.29 "@tsconfig/recommended": ^1.0.3 @@ -12547,7 +12547,7 @@ __metadata: dependencies: "@jest/globals": ^29.5.0 "@langchain/core": ">0.1.0 <0.3.0" - "@langchain/scripts": ^0.0.23 + "@langchain/scripts": ">=0.1.0 <0.2.0" "@swc/core": ^1.3.90 "@swc/jest": ^0.2.29 "@tsconfig/recommended": ^1.0.3 @@ -26692,7 +26692,7 @@ __metadata: "@langchain/pinecone": "workspace:*" "@langchain/qdrant": "workspace:*" "@langchain/redis": "workspace:*" - "@langchain/scripts": ^0.0.23 + "@langchain/scripts": ">=0.1.0 <0.2.0" "@langchain/textsplitters": "workspace:*" "@langchain/weaviate": "workspace:*" "@langchain/yandex": "workspace:*" @@ -32057,7 +32057,7 @@ __metadata: "@langchain/mistralai": ^0.0.26 "@langchain/ollama": ^0.0.2 "@langchain/openai": ">=0.1.0 <0.3.0" - "@langchain/scripts": ^0.0.23 + "@langchain/scripts": ">=0.1.0 <0.2.0" "@langchain/textsplitters": ~0.0.0 "@mendable/firecrawl-js": ^0.0.13 "@notionhq/client": ^2.2.10 From 8acc4a867eb8691ff0bb590dd0ada36561a0188a Mon Sep 17 00:00:00 2001 From: "marsal.sans" Date: Fri, 23 Aug 2024 00:58:57 +0200 Subject: [PATCH 087/344] google-common[minor]: export GoogleAISafetyError type (#6582) Co-authored-by: Brace Sproul --- libs/langchain-google-common/src/index.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/libs/langchain-google-common/src/index.ts b/libs/langchain-google-common/src/index.ts index 18e62d415f9d..373617c72e55 100644 --- a/libs/langchain-google-common/src/index.ts +++ b/libs/langchain-google-common/src/index.ts @@ -8,3 +8,4 @@ export * from "./types.js"; export * from "./utils/stream.js"; export * from "./utils/common.js"; export * from "./utils/zod_to_gemini_parameters.js"; +export * from "./utils/safety.js"; From 89cb9cb049442f6139d4e75d301450015123bcbc Mon Sep 17 00:00:00 2001 From: nick-w-nick <43578531+nick-w-nick@users.noreply.github.com> Date: Thu, 22 Aug 2024 18:59:10 -0400 Subject: [PATCH 088/344] docs[minor]: Add missing link to Runnables guide (#6566) Co-authored-by: Bagatur Co-authored-by: Brace Sproul --- docs/core_docs/docs/how_to/sequence.ipynb | 24 ++++++++++++++++++----- 1 file changed, 19 insertions(+), 5 deletions(-) diff --git a/docs/core_docs/docs/how_to/sequence.ipynb b/docs/core_docs/docs/how_to/sequence.ipynb index 869a7b983c22..715b6bf704b6 100644 --- a/docs/core_docs/docs/how_to/sequence.ipynb +++ b/docs/core_docs/docs/how_to/sequence.ipynb @@ -63,6 +63,21 @@ "```" ] }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "// @lc-docs-hide-cell\n", + "import { ChatOpenAI } from '@langchain/openai';\n", + "\n", + "const model = new ChatOpenAI({\n", + " model: \"gpt-4o\",\n", + " temperature: 0,\n", + "})" + ] + }, { "cell_type": "code", "execution_count": 2, @@ -72,7 +87,6 @@ "import { StringOutputParser } from \"@langchain/core/output_parsers\";\n", "import { ChatPromptTemplate } from \"@langchain/core/prompts\";\n", "\n", - "\n", "const prompt = ChatPromptTemplate.fromTemplate(\"tell me a joke about {topic}\")\n", "\n", "const chain = prompt.pipe(model).pipe(new StringOutputParser())" @@ -142,7 +156,7 @@ "const analysisPrompt = ChatPromptTemplate.fromTemplate(\"is this a funny joke? {joke}\")\n", "\n", "const composedChain = new RunnableLambda({\n", - " func: async (input) => {\n", + " func: async (input: { topic: string }) => {\n", " const result = await chain.invoke(input);\n", " return { joke: result };\n", " }\n", @@ -210,15 +224,15 @@ "\n", "You now know some ways to chain two runnables together.\n", "\n", - "To learn more, see the other how-to guides on runnables in this section." + "To learn more, see the other how-to guides on runnables in [this section](/docs/how_to/#langchain-expression-language-lcel)." ] } ], "metadata": { "kernelspec": { - "display_name": "Deno", + "display_name": "TypeScript", "language": "typescript", - "name": "deno" + "name": "tslab" }, "language_info": { "codemirror_mode": { From f4f50f9be781ebee9b387ad12a8dbac4897e7e2f Mon Sep 17 00:00:00 2001 From: Damian Schenkelman Date: Thu, 22 Aug 2024 19:59:28 -0300 Subject: [PATCH 089/344] langchain[minor]: Support storing and retrieving Document id field in MemoryVectorStore (#6572) Co-authored-by: dschenkelman Co-authored-by: Brace Sproul --- langchain/src/vectorstores/memory.ts | 6 +++ .../src/vectorstores/tests/memory.test.ts | 38 +++++++++++++++++++ 2 files changed, 44 insertions(+) diff --git a/langchain/src/vectorstores/memory.ts b/langchain/src/vectorstores/memory.ts index fc77840a77ab..d6210a95f1cc 100644 --- a/langchain/src/vectorstores/memory.ts +++ b/langchain/src/vectorstores/memory.ts @@ -17,6 +17,7 @@ interface MemoryVector { embedding: number[]; // eslint-disable-next-line @typescript-eslint/no-explicit-any metadata: Record; + id?: string; } /** @@ -186,6 +187,7 @@ export class MemoryVectorStore extends VectorStore { content: documents[idx].pageContent, embedding, metadata: documents[idx].metadata, + id: documents[idx].id, })); this.memoryVectors = this.memoryVectors.concat(memoryVectors); @@ -204,6 +206,7 @@ export class MemoryVectorStore extends VectorStore { const doc = new Document({ metadata: memoryVector.metadata, pageContent: memoryVector.content, + id: memoryVector.id, }); return filter(doc); }; @@ -215,6 +218,7 @@ export class MemoryVectorStore extends VectorStore { metadata: vector.metadata, content: vector.content, embedding: vector.embedding, + id: vector.id, })) .sort((a, b) => (a.similarity > b.similarity ? -1 : 0)) .slice(0, k); @@ -240,6 +244,7 @@ export class MemoryVectorStore extends VectorStore { new Document({ metadata: search.metadata, pageContent: search.content, + id: search.id, }), search.similarity, ]); @@ -273,6 +278,7 @@ export class MemoryVectorStore extends VectorStore { new Document({ metadata: searches[idx].metadata, pageContent: searches[idx].content, + id: searches[idx].id, }) ); } diff --git a/langchain/src/vectorstores/tests/memory.test.ts b/langchain/src/vectorstores/tests/memory.test.ts index 92ad7839ff48..02b893aa0cfd 100644 --- a/langchain/src/vectorstores/tests/memory.test.ts +++ b/langchain/src/vectorstores/tests/memory.test.ts @@ -30,6 +30,44 @@ test("MemoryVectorStore with external ids", async () => { ]); }); +test("MemoryVectorStore stores and retrieves document IDs", async () => { + const embeddings = new SyntheticEmbeddings({ + vectorSize: 1536, + }); + const store = new MemoryVectorStore(embeddings); + + const filterFunc = (doc: DocumentInterface): boolean => { + const { metadata } = doc; + if (metadata.namespace <= 2) { + return true; + } + return false; + }; + + const retriever = store.asRetriever({ + k: 2, + filter: filterFunc, + }); + + expect(retriever).toBeDefined(); + + await retriever.addDocuments([ + { pageContent: "hello", metadata: { namespace: 1 }, id: "1" }, + { pageContent: "hello", metadata: { namespace: 2 }, id: "2" }, + { pageContent: "hello", metadata: { namespace: 3 }, id: "3" }, + { pageContent: "hello", metadata: { namespace: 4 }, id: "4" }, + ]); + + const results = await retriever.getRelevantDocuments("hello"); + + expect(results).toHaveLength(2); + + expect(results).toEqual([ + new Document({ metadata: { namespace: 1 }, pageContent: "hello", id: "1" }), + new Document({ metadata: { namespace: 2 }, pageContent: "hello", id: "2" }), + ]); +}); + test("MemoryVectorStore as retriever can filter metadata", async () => { const embeddings = new SyntheticEmbeddings({ vectorSize: 1536, From c541d66bf269fa7b2c99fc348787567977ff6f18 Mon Sep 17 00:00:00 2001 From: Brace Sproul Date: Thu, 22 Aug 2024 17:57:59 -0700 Subject: [PATCH 090/344] google-common[patch]: Release 0.0.27 (#6608) --- libs/langchain-google-common/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libs/langchain-google-common/package.json b/libs/langchain-google-common/package.json index c913b36f38bd..242093637b64 100644 --- a/libs/langchain-google-common/package.json +++ b/libs/langchain-google-common/package.json @@ -1,6 +1,6 @@ { "name": "@langchain/google-common", - "version": "0.0.26", + "version": "0.0.27", "description": "Core types and classes for Google services.", "type": "module", "engines": { From 94aa109b300ec664f9ab586e07776193baed83ff Mon Sep 17 00:00:00 2001 From: Brace Sproul Date: Thu, 22 Aug 2024 18:02:16 -0700 Subject: [PATCH 091/344] google[patch]: Release 0.0.27 (#6609) --- libs/langchain-google-gauth/package.json | 4 +-- .../package.json | 6 ++-- libs/langchain-google-vertexai/package.json | 6 ++-- libs/langchain-google-webauth/package.json | 4 +-- yarn.lock | 30 ++++++++++++------- 5 files changed, 30 insertions(+), 20 deletions(-) diff --git a/libs/langchain-google-gauth/package.json b/libs/langchain-google-gauth/package.json index 3924cb8b97b2..e70fa0a15204 100644 --- a/libs/langchain-google-gauth/package.json +++ b/libs/langchain-google-gauth/package.json @@ -1,6 +1,6 @@ { "name": "@langchain/google-gauth", - "version": "0.0.26", + "version": "0.0.27", "description": "Google auth based authentication support for Google services", "type": "module", "engines": { @@ -36,7 +36,7 @@ "license": "MIT", "dependencies": { "@langchain/core": ">=0.2.21 <0.3.0", - "@langchain/google-common": "~0.0.26", + "@langchain/google-common": "~0.0.27", "google-auth-library": "^8.9.0" }, "devDependencies": { diff --git a/libs/langchain-google-vertexai-web/package.json b/libs/langchain-google-vertexai-web/package.json index 105600328c59..c4429f8e326b 100644 --- a/libs/langchain-google-vertexai-web/package.json +++ b/libs/langchain-google-vertexai-web/package.json @@ -1,6 +1,6 @@ { "name": "@langchain/google-vertexai-web", - "version": "0.0.26", + "version": "0.0.27", "description": "LangChain.js support for Google Vertex AI Web", "type": "module", "engines": { @@ -33,11 +33,11 @@ "license": "MIT", "dependencies": { "@langchain/core": ">=0.2.21 <0.3.0", - "@langchain/google-webauth": "~0.0.26" + "@langchain/google-webauth": "~0.0.27" }, "devDependencies": { "@jest/globals": "^29.5.0", - "@langchain/google-common": "~0.0", + "@langchain/google-common": "^0.0.27", "@langchain/scripts": ">=0.1.0 <0.2.0", "@langchain/standard-tests": "0.0.0", "@swc/core": "^1.3.90", diff --git a/libs/langchain-google-vertexai/package.json b/libs/langchain-google-vertexai/package.json index 143e7505ce17..5fcea15ee516 100644 --- a/libs/langchain-google-vertexai/package.json +++ b/libs/langchain-google-vertexai/package.json @@ -1,6 +1,6 @@ { "name": "@langchain/google-vertexai", - "version": "0.0.26", + "version": "0.0.27", "description": "LangChain.js support for Google Vertex AI", "type": "module", "engines": { @@ -33,11 +33,11 @@ "license": "MIT", "dependencies": { "@langchain/core": ">=0.2.21 <0.3.0", - "@langchain/google-gauth": "~0.0.26" + "@langchain/google-gauth": "~0.0.27" }, "devDependencies": { "@jest/globals": "^29.5.0", - "@langchain/google-common": "~0.0", + "@langchain/google-common": "^0.0.27", "@langchain/scripts": ">=0.1.0 <0.2.0", "@langchain/standard-tests": "0.0.0", "@swc/core": "^1.3.90", diff --git a/libs/langchain-google-webauth/package.json b/libs/langchain-google-webauth/package.json index 3ed28832fea2..320f731a088b 100644 --- a/libs/langchain-google-webauth/package.json +++ b/libs/langchain-google-webauth/package.json @@ -1,6 +1,6 @@ { "name": "@langchain/google-webauth", - "version": "0.0.26", + "version": "0.0.27", "description": "Web-based authentication support for Google services", "type": "module", "engines": { @@ -33,7 +33,7 @@ "license": "MIT", "dependencies": { "@langchain/core": ">=0.2.21 <0.3.0", - "@langchain/google-common": "~0.0.26", + "@langchain/google-common": "~0.0.27", "web-auth-library": "^1.0.3" }, "devDependencies": { diff --git a/yarn.lock b/yarn.lock index 8a47d3436178..4db119d10fb2 100644 --- a/yarn.lock +++ b/yarn.lock @@ -11760,7 +11760,7 @@ __metadata: languageName: unknown linkType: soft -"@langchain/google-common@workspace:*, @langchain/google-common@workspace:libs/langchain-google-common, @langchain/google-common@~0.0, @langchain/google-common@~0.0.26": +"@langchain/google-common@^0.0.27, @langchain/google-common@workspace:*, @langchain/google-common@workspace:libs/langchain-google-common, @langchain/google-common@~0.0.27": version: 0.0.0-use.local resolution: "@langchain/google-common@workspace:libs/langchain-google-common" dependencies: @@ -11793,13 +11793,13 @@ __metadata: languageName: unknown linkType: soft -"@langchain/google-gauth@workspace:libs/langchain-google-gauth, @langchain/google-gauth@~0.0.26": +"@langchain/google-gauth@workspace:libs/langchain-google-gauth, @langchain/google-gauth@~0.0.26, @langchain/google-gauth@~0.0.27": version: 0.0.0-use.local resolution: "@langchain/google-gauth@workspace:libs/langchain-google-gauth" dependencies: "@jest/globals": ^29.5.0 "@langchain/core": ">=0.2.21 <0.3.0" - "@langchain/google-common": ~0.0.26 + "@langchain/google-common": ~0.0.27 "@langchain/scripts": ">=0.1.0 <0.2.0" "@swc/core": ^1.3.90 "@swc/jest": ^0.2.29 @@ -11867,8 +11867,8 @@ __metadata: dependencies: "@jest/globals": ^29.5.0 "@langchain/core": ">=0.2.21 <0.3.0" - "@langchain/google-common": ~0.0 - "@langchain/google-webauth": ~0.0.26 + "@langchain/google-common": ^0.0.27 + "@langchain/google-webauth": ~0.0.27 "@langchain/scripts": ">=0.1.0 <0.2.0" "@langchain/standard-tests": 0.0.0 "@swc/core": ^1.3.90 @@ -11895,14 +11895,24 @@ __metadata: languageName: unknown linkType: soft -"@langchain/google-vertexai@^0.0.26, @langchain/google-vertexai@workspace:*, @langchain/google-vertexai@workspace:libs/langchain-google-vertexai": +"@langchain/google-vertexai@npm:^0.0.26": + version: 0.0.26 + resolution: "@langchain/google-vertexai@npm:0.0.26" + dependencies: + "@langchain/core": ">=0.2.21 <0.3.0" + "@langchain/google-gauth": ~0.0.26 + checksum: 3ffb04cbf50f1b361178df4eac9fbd711f21405e6ad882a1d16e435cec382d9d9d52b02e47adc3a6b5e5b5cdf7c43cfd3ff0c7bdaaf8ff9e9bc0245e6beadbc2 + languageName: node + linkType: hard + +"@langchain/google-vertexai@workspace:*, @langchain/google-vertexai@workspace:libs/langchain-google-vertexai": version: 0.0.0-use.local resolution: "@langchain/google-vertexai@workspace:libs/langchain-google-vertexai" dependencies: "@jest/globals": ^29.5.0 "@langchain/core": ">=0.2.21 <0.3.0" - "@langchain/google-common": ~0.0 - "@langchain/google-gauth": ~0.0.26 + "@langchain/google-common": ^0.0.27 + "@langchain/google-gauth": ~0.0.27 "@langchain/scripts": ">=0.1.0 <0.2.0" "@langchain/standard-tests": 0.0.0 "@swc/core": ^1.3.90 @@ -11929,13 +11939,13 @@ __metadata: languageName: unknown linkType: soft -"@langchain/google-webauth@workspace:libs/langchain-google-webauth, @langchain/google-webauth@~0.0.26": +"@langchain/google-webauth@workspace:libs/langchain-google-webauth, @langchain/google-webauth@~0.0.27": version: 0.0.0-use.local resolution: "@langchain/google-webauth@workspace:libs/langchain-google-webauth" dependencies: "@jest/globals": ^29.5.0 "@langchain/core": ">=0.2.21 <0.3.0" - "@langchain/google-common": ~0.0.26 + "@langchain/google-common": ~0.0.27 "@langchain/scripts": ">=0.1.0 <0.2.0" "@swc/core": ^1.3.90 "@swc/jest": ^0.2.29 From a28c6d409312d03ca44cb0e564107c3daa3d83a4 Mon Sep 17 00:00:00 2001 From: Brace Sproul Date: Thu, 22 Aug 2024 18:07:44 -0700 Subject: [PATCH 092/344] scripts[patch]: Revert change to delete folder func (#6607) --- libs/langchain-scripts/package.json | 2 +- libs/langchain-scripts/src/build/index.ts | 79 ++++++++++------------- 2 files changed, 36 insertions(+), 45 deletions(-) diff --git a/libs/langchain-scripts/package.json b/libs/langchain-scripts/package.json index 7b5758284a9b..258423db68b8 100644 --- a/libs/langchain-scripts/package.json +++ b/libs/langchain-scripts/package.json @@ -1,6 +1,6 @@ { "name": "@langchain/scripts", - "version": "0.1.0", + "version": "0.1.1", "description": "Shared scripts for LangChain.js", "type": "module", "engines": { diff --git a/libs/langchain-scripts/src/build/index.ts b/libs/langchain-scripts/src/build/index.ts index 0efb6f720379..5a02a2e9ff45 100644 --- a/libs/langchain-scripts/src/build/index.ts +++ b/libs/langchain-scripts/src/build/index.ts @@ -5,7 +5,6 @@ import { Command } from "commander"; import { rollup } from "@rollup/wasm-node"; import path from "node:path"; import { glob } from "glob"; -import { setTimeout } from "node:timers/promises"; import { ExportsMapValue, ImportData, LangChainConfig } from "../types.js"; async function asyncSpawn(command: string, args: string[]) { @@ -29,55 +28,47 @@ async function asyncSpawn(command: string, args: string[]) { }); } -const deleteFolderRecursive = async function ( - inputPath: string, - retries = 3, - delay = 100 -) { - for (let attempt = 0; attempt < retries; attempt += 1) { - try { - // Verify the path exists - if ( - await fs.promises - .access(inputPath) - .then(() => true) - .catch(() => false) - ) { - const pathStat = await fs.promises.lstat(inputPath); - // If it's a file, delete it and return - if (pathStat.isFile()) { - await fs.promises.unlink(inputPath); - } else if (pathStat.isDirectory()) { - // List contents of directory - const directoryContents = await fs.promises.readdir(inputPath); - if (directoryContents.length) { - for await (const item of directoryContents) { - await deleteFolderRecursive( - path.join(inputPath, item), - retries, - delay - ); +const deleteFolderRecursive = async function (inputPath: string) { + try { + // Verify the path exists + if ( + await fs.promises + .access(inputPath) + .then(() => true) + .catch(() => false) + ) { + const pathStat = await fs.promises.lstat(inputPath); + // If it's a file, delete it and return + if (pathStat.isFile()) { + await fs.promises.unlink(inputPath); + } else if (pathStat.isDirectory()) { + // List contents of directory + const directoryContents = await fs.promises.readdir(inputPath); + if (directoryContents.length) { + for await (const item of directoryContents) { + const itemStat = await fs.promises.lstat( + path.join(inputPath, item) + ); + if (itemStat.isFile()) { + // Delete file + await fs.promises.unlink(path.join(inputPath, item)); + } else if (itemStat.isDirectory()) { + await deleteFolderRecursive(path.join(inputPath, item)); } } - // If the directory is empty or all contents have been deleted, delete it + } else if (directoryContents.length === 0) { + // If the directory is empty, delete it await fs.promises.rmdir(inputPath); } } - // If we reach here, the operation was successful - return; - // eslint-disable-next-line @typescript-eslint/no-explicit-any - } catch (error: any) { - if (error.code === "ENOENT") { - // File or directory doesn't exist, consider it deleted - return; - } - if (attempt === retries - 1) { - // If this was the last attempt, throw the error - throw error; - } - // Wait before the next attempt - await setTimeout(delay); } + // eslint-disable-next-line @typescript-eslint/no-explicit-any + } catch (error: any) { + if (error.code !== "ENOENT") { + // If the error is not "file or directory doesn't exist", rethrow it + throw error; + } + // Otherwise, ignore the error (file or directory already doesn't exist) } }; From c622c546524a344a6b136b5a3cb62b193dbe758c Mon Sep 17 00:00:00 2001 From: Brace Sproul Date: Thu, 22 Aug 2024 18:09:12 -0700 Subject: [PATCH 093/344] community[patch]: Release 0.2.31 (#6610) --- libs/langchain-community/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libs/langchain-community/package.json b/libs/langchain-community/package.json index 2f3767be39ac..767540e6f2fd 100644 --- a/libs/langchain-community/package.json +++ b/libs/langchain-community/package.json @@ -1,6 +1,6 @@ { "name": "@langchain/community", - "version": "0.2.30", + "version": "0.2.31", "description": "Third-party integrations for LangChain.js", "type": "module", "engines": { From 81af1fc7b33f6f07856af0a485da194762862264 Mon Sep 17 00:00:00 2001 From: Brace Sproul Date: Thu, 22 Aug 2024 18:14:11 -0700 Subject: [PATCH 094/344] Release 0.2.17 (#6611) --- langchain/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/langchain/package.json b/langchain/package.json index 5c83ce64f9ee..ea81f73ccef1 100644 --- a/langchain/package.json +++ b/langchain/package.json @@ -1,6 +1,6 @@ { "name": "langchain", - "version": "0.2.16", + "version": "0.2.17", "description": "Typescript bindings for langchain", "type": "module", "engines": { From 377ad8b9500321e8f297b1d9cbc0600a68bc01f4 Mon Sep 17 00:00:00 2001 From: Brace Sproul Date: Thu, 22 Aug 2024 21:32:24 -0700 Subject: [PATCH 095/344] langchain[patch]: Bump vertex dep (#6613) --- langchain/package.json | 2 +- yarn.lock | 16 +++------------- 2 files changed, 4 insertions(+), 14 deletions(-) diff --git a/langchain/package.json b/langchain/package.json index ea81f73ccef1..3465af3054fe 100644 --- a/langchain/package.json +++ b/langchain/package.json @@ -609,7 +609,7 @@ "@langchain/aws": "^0.0.5", "@langchain/cohere": "^0.2.1", "@langchain/google-genai": "^0.0.26", - "@langchain/google-vertexai": "^0.0.26", + "@langchain/google-vertexai": "~0.0", "@langchain/groq": "^0.0.15", "@langchain/mistralai": "^0.0.26", "@langchain/ollama": "^0.0.2", diff --git a/yarn.lock b/yarn.lock index 4db119d10fb2..2548d54d63d0 100644 --- a/yarn.lock +++ b/yarn.lock @@ -11793,7 +11793,7 @@ __metadata: languageName: unknown linkType: soft -"@langchain/google-gauth@workspace:libs/langchain-google-gauth, @langchain/google-gauth@~0.0.26, @langchain/google-gauth@~0.0.27": +"@langchain/google-gauth@workspace:libs/langchain-google-gauth, @langchain/google-gauth@~0.0.27": version: 0.0.0-use.local resolution: "@langchain/google-gauth@workspace:libs/langchain-google-gauth" dependencies: @@ -11895,17 +11895,7 @@ __metadata: languageName: unknown linkType: soft -"@langchain/google-vertexai@npm:^0.0.26": - version: 0.0.26 - resolution: "@langchain/google-vertexai@npm:0.0.26" - dependencies: - "@langchain/core": ">=0.2.21 <0.3.0" - "@langchain/google-gauth": ~0.0.26 - checksum: 3ffb04cbf50f1b361178df4eac9fbd711f21405e6ad882a1d16e435cec382d9d9d52b02e47adc3a6b5e5b5cdf7c43cfd3ff0c7bdaaf8ff9e9bc0245e6beadbc2 - languageName: node - linkType: hard - -"@langchain/google-vertexai@workspace:*, @langchain/google-vertexai@workspace:libs/langchain-google-vertexai": +"@langchain/google-vertexai@workspace:*, @langchain/google-vertexai@workspace:libs/langchain-google-vertexai, @langchain/google-vertexai@~0.0": version: 0.0.0-use.local resolution: "@langchain/google-vertexai@workspace:libs/langchain-google-vertexai" dependencies: @@ -32062,7 +32052,7 @@ __metadata: "@langchain/cohere": ^0.2.1 "@langchain/core": ">=0.2.21 <0.3.0" "@langchain/google-genai": ^0.0.26 - "@langchain/google-vertexai": ^0.0.26 + "@langchain/google-vertexai": ~0.0 "@langchain/groq": ^0.0.15 "@langchain/mistralai": ^0.0.26 "@langchain/ollama": ^0.0.2 From e15307fa41cb09079a7cf74dec9fa516b480c8b1 Mon Sep 17 00:00:00 2001 From: Brace Sproul Date: Fri, 23 Aug 2024 12:16:00 -0700 Subject: [PATCH 096/344] cohere[patch]: Do not test stream tool calls in cohere (#6617) --- .../src/tests/chat_models.standard.int.test.ts | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/libs/langchain-cohere/src/tests/chat_models.standard.int.test.ts b/libs/langchain-cohere/src/tests/chat_models.standard.int.test.ts index e9586d0107e9..db0f8df4dba1 100644 --- a/libs/langchain-cohere/src/tests/chat_models.standard.int.test.ts +++ b/libs/langchain-cohere/src/tests/chat_models.standard.int.test.ts @@ -54,6 +54,14 @@ class ChatCohereStandardIntegrationTests extends ChatModelIntegrationTests< "Prompt does not always cause Cohere to invoke a tool. TODO: re-write inside this class with better prompting for cohere." ); } + + async testStreamTools(): Promise { + this.skipTestMessage( + "testStreamTools", + "ChatCohere", + "Cohere only responds with the tool call in the final chunk. TODO: fix implementation to actually stream tools." + ); + } } const testClass = new ChatCohereStandardIntegrationTests(); From 0cd64d85dba1f02fd1b0fc4762d1ed310ebf805a Mon Sep 17 00:00:00 2001 From: Brace Sproul Date: Fri, 23 Aug 2024 14:40:34 -0700 Subject: [PATCH 097/344] scripts[patch]: Use fs.rm for deleting directories (#6619) --- libs/langchain-scripts/package.json | 2 +- libs/langchain-scripts/src/build/index.ts | 107 ++++++++-------------- 2 files changed, 40 insertions(+), 69 deletions(-) diff --git a/libs/langchain-scripts/package.json b/libs/langchain-scripts/package.json index 258423db68b8..13e9167a436b 100644 --- a/libs/langchain-scripts/package.json +++ b/libs/langchain-scripts/package.json @@ -1,6 +1,6 @@ { "name": "@langchain/scripts", - "version": "0.1.1", + "version": "0.1.2", "description": "Shared scripts for LangChain.js", "type": "module", "engines": { diff --git a/libs/langchain-scripts/src/build/index.ts b/libs/langchain-scripts/src/build/index.ts index 5a02a2e9ff45..f224d3678383 100644 --- a/libs/langchain-scripts/src/build/index.ts +++ b/libs/langchain-scripts/src/build/index.ts @@ -28,47 +28,25 @@ async function asyncSpawn(command: string, args: string[]) { }); } -const deleteFolderRecursive = async function (inputPath: string) { +const fsRmRfSafe = async (inputPath: string) => { try { - // Verify the path exists - if ( - await fs.promises - .access(inputPath) - .then(() => true) - .catch(() => false) - ) { - const pathStat = await fs.promises.lstat(inputPath); - // If it's a file, delete it and return - if (pathStat.isFile()) { - await fs.promises.unlink(inputPath); - } else if (pathStat.isDirectory()) { - // List contents of directory - const directoryContents = await fs.promises.readdir(inputPath); - if (directoryContents.length) { - for await (const item of directoryContents) { - const itemStat = await fs.promises.lstat( - path.join(inputPath, item) - ); - if (itemStat.isFile()) { - // Delete file - await fs.promises.unlink(path.join(inputPath, item)); - } else if (itemStat.isDirectory()) { - await deleteFolderRecursive(path.join(inputPath, item)); - } - } - } else if (directoryContents.length === 0) { - // If the directory is empty, delete it - await fs.promises.rmdir(inputPath); - } - } - } + await fs.promises.rm(inputPath, { recursive: true, force: true }); // eslint-disable-next-line @typescript-eslint/no-explicit-any } catch (error: any) { - if (error.code !== "ENOENT") { - // If the error is not "file or directory doesn't exist", rethrow it - throw error; - } - // Otherwise, ignore the error (file or directory already doesn't exist) + console.log( + `Error deleting directory via fs.promises.rm: ${error.code}. Path: ${inputPath}` + ); + } +}; + +const fsUnlinkSafe = async (filePath: string) => { + try { + await fs.promises.unlink(filePath); + // eslint-disable-next-line @typescript-eslint/no-explicit-any + } catch (error: any) { + console.log( + `Error deleting file via fs.promises.unlink: ${error.code}. Path: ${filePath}` + ); } }; @@ -611,15 +589,11 @@ function processOptions(): { async function cleanGeneratedFiles(config: LangChainConfig) { const allFileNames = Object.keys(config.entrypoints) - .map((key) => [`${key}.cjs`, `${key}.js`, `${key}.d.ts`, `${key}.d.dts`]) + .map((key) => [`${key}.cjs`, `${key}.js`, `${key}.d.ts`]) .flat(); return Promise.all( allFileNames.map(async (fileName) => { - try { - await fs.promises.unlink(fileName); - } catch { - // no-op - } + await fsUnlinkSafe(fileName); }) ); } @@ -637,8 +611,9 @@ export async function moveAndRename({ return; } + let renamedDestination = ""; try { - for (const file of await fs.promises.readdir(abs(source), { + for await (const file of await fs.promises.readdir(abs(source), { withFileTypes: true, })) { if (file.isDirectory()) { @@ -667,17 +642,23 @@ export async function moveAndRename({ // Rename the file to .cjs const renamed = path.format({ name: parsed.name, ext: ".cjs" }); - - await fs.promises.writeFile( - abs(`${dest}/${renamed}`), - rewritten, - "utf8" + renamedDestination = abs(`${dest}/${renamed}`); + await fs.promises.writeFile(renamedDestination, rewritten, "utf8"); + } + } + // eslint-disable-next-line @typescript-eslint/no-explicit-any + } catch (error: any) { + console.error("Error during moveAndRename"); + if (error.code === "ENOENT") { + // Check if file already exists in destination + if (fs.existsSync(renamedDestination)) { + console.error( + `File already exists in destination: ${renamedDestination}` ); + } else { + console.error(`File not found: ${error.path}`); } } - } catch (err) { - console.error(err); - process.exit(1); } } @@ -702,14 +683,10 @@ export async function buildWithTSup() { // Clean & generate build files if (pre && shouldGenMaps) { await Promise.all([ - deleteFolderRecursive("dist").catch((e) => { + fsRmRfSafe("dist").catch((e) => { console.error("Error removing dist (pre && shouldGenMaps)"); throw e; }), - deleteFolderRecursive(".turbo").catch((e) => { - console.error("Error removing .turbo (pre && shouldGenMaps)"); - throw e; - }), cleanGeneratedFiles(config), createImportMapFile(config), generateImportConstants(config), @@ -717,14 +694,10 @@ export async function buildWithTSup() { ]); } else if (pre && !shouldGenMaps) { await Promise.all([ - deleteFolderRecursive("dist").catch((e) => { + fsRmRfSafe("dist").catch((e) => { console.error("Error removing dist (pre && !shouldGenMaps)"); throw e; }), - deleteFolderRecursive(".turbo").catch((e) => { - console.error("Error deleting with deleteFolderRecursive"); - throw e; - }), cleanGeneratedFiles(config), ]); } @@ -742,11 +715,11 @@ export async function buildWithTSup() { // move CJS to dist await Promise.all([ updatePackageJson(config), - deleteFolderRecursive("dist-cjs").catch((e) => { + fsRmRfSafe("dist-cjs").catch((e) => { console.error("Error removing dist-cjs"); throw e; }), - deleteFolderRecursive("dist/tests").catch((e) => { + fsRmRfSafe("dist/tests").catch((e) => { console.error("Error removing dist/tests"); throw e; }), @@ -754,9 +727,7 @@ export async function buildWithTSup() { // Required for cross-platform compatibility. // Windows does not manage globs the same as Max/Linux when deleting directories. const testFolders = await glob("dist/**/tests"); - await Promise.all( - testFolders.map((folder) => deleteFolderRecursive(folder)) - ); + await Promise.all(testFolders.map((folder) => fsRmRfSafe(folder))); })().catch((e) => { console.error("Error removing dist/**/tests"); throw e; From 037cb6f5bad31f8b30d5cddec3704727473cfe30 Mon Sep 17 00:00:00 2001 From: Tat Dat Duong Date: Mon, 26 Aug 2024 15:41:09 +0200 Subject: [PATCH 098/344] fix(core): return conditional edge in toJSON --- langchain-core/src/runnables/graph.ts | 27 +++++++++++++++------------ 1 file changed, 15 insertions(+), 12 deletions(-) diff --git a/langchain-core/src/runnables/graph.ts b/langchain-core/src/runnables/graph.ts index 880305b5bb2c..1137b591da5c 100644 --- a/langchain-core/src/runnables/graph.ts +++ b/langchain-core/src/runnables/graph.ts @@ -68,18 +68,21 @@ export class Graph { id: stableNodeIds[node.id], ...nodeDataJson(node), })), - edges: this.edges.map((edge) => - edge.data - ? { - source: stableNodeIds[edge.source], - target: stableNodeIds[edge.target], - data: edge.data, - } - : { - source: stableNodeIds[edge.source], - target: stableNodeIds[edge.target], - } - ), + edges: this.edges.map((edge) => { + const item: Record = { + source: stableNodeIds[edge.source], + target: stableNodeIds[edge.target], + }; + + if (typeof edge.data !== "undefined") { + item.data = edge.data; + } + + if (typeof edge.conditional !== "undefined") { + item.conditional = edge.conditional; + } + return item; + }), }; } From d3dfa92c9635fa5671e956aa66b2daec168c3d8e Mon Sep 17 00:00:00 2001 From: Brace Sproul Date: Mon, 26 Aug 2024 09:08:01 -0700 Subject: [PATCH 099/344] langchain[patch]: Update version for removal of deprecated code (#6620) --- langchain/src/chains/conversational_retrieval_chain.ts | 2 +- langchain/src/chains/llm_chain.ts | 2 +- langchain/src/chains/retrieval_qa.ts | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/langchain/src/chains/conversational_retrieval_chain.ts b/langchain/src/chains/conversational_retrieval_chain.ts index c5b5b75f479d..8e9ab9c2b83c 100644 --- a/langchain/src/chains/conversational_retrieval_chain.ts +++ b/langchain/src/chains/conversational_retrieval_chain.ts @@ -33,7 +33,7 @@ export interface ConversationalRetrievalQAChainInput extends ChainInputs { } /** - * @deprecated This class will be removed in 0.3.0. See below for an example implementation using + * @deprecated This class will be removed in 1.0.0. See below for an example implementation using * `createRetrievalChain`. * * Class for conducting conversational question-answering tasks with a diff --git a/langchain/src/chains/llm_chain.ts b/langchain/src/chains/llm_chain.ts index f647546ed369..46872e4e13c4 100644 --- a/langchain/src/chains/llm_chain.ts +++ b/langchain/src/chains/llm_chain.ts @@ -72,7 +72,7 @@ function _getLanguageModel(llmLike: RunnableInterface): BaseLanguageModel { } /** - * @deprecated This class will be removed in 0.3.0. Use the LangChain Expression Language (LCEL) instead. + * @deprecated This class will be removed in 1.0.0. Use the LangChain Expression Language (LCEL) instead. * See the example below for how to use LCEL with the LLMChain class: * * Chain to run queries against LLMs. diff --git a/langchain/src/chains/retrieval_qa.ts b/langchain/src/chains/retrieval_qa.ts index 36e13af4242b..61c644f43daf 100644 --- a/langchain/src/chains/retrieval_qa.ts +++ b/langchain/src/chains/retrieval_qa.ts @@ -23,7 +23,7 @@ export interface RetrievalQAChainInput extends Omit { } /** - * @deprecated This class will be removed in 0.3.0. See below for an example implementation using + * @deprecated This class will be removed in 1.0.0. See below for an example implementation using * `createRetrievalChain`: * Class representing a chain for performing question-answering tasks with * a retrieval component. From 3193cc44cd374f431c0bc5ee6f8bde559a102ca5 Mon Sep 17 00:00:00 2001 From: Brace Sproul Date: Mon, 26 Aug 2024 10:02:38 -0700 Subject: [PATCH 100/344] mistral[patch]: Hide secrets (#6630) --- libs/langchain-mistralai/src/chat_models.ts | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/libs/langchain-mistralai/src/chat_models.ts b/libs/langchain-mistralai/src/chat_models.ts index 2d78b74334e1..18dee4c44fe1 100644 --- a/libs/langchain-mistralai/src/chat_models.ts +++ b/libs/langchain-mistralai/src/chat_models.ts @@ -936,6 +936,18 @@ export class ChatMistralAI< this.addAllHooksToHttpClient(); } + get lc_secrets(): { [key: string]: string } | undefined { + return { + apiKey: "MISTRAL_API_KEY", + }; + } + + get lc_aliases(): { [key: string]: string } | undefined { + return { + apiKey: "mistral_api_key", + }; + } + getLsParams(options: this["ParsedCallOptions"]): LangSmithParams { const params = this.invocationParams(options); return { From 5a827d2480a1a85807eeeb99c3713bcea2560861 Mon Sep 17 00:00:00 2001 From: Brace Sproul Date: Mon, 26 Aug 2024 10:05:53 -0700 Subject: [PATCH 101/344] mistral[patch]: Release 0.0.29 (#6632) --- libs/langchain-mistralai/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libs/langchain-mistralai/package.json b/libs/langchain-mistralai/package.json index 5e1b587975f8..59eb0b7b9527 100644 --- a/libs/langchain-mistralai/package.json +++ b/libs/langchain-mistralai/package.json @@ -1,6 +1,6 @@ { "name": "@langchain/mistralai", - "version": "0.0.28", + "version": "0.0.29", "description": "MistralAI integration for LangChain.js", "type": "module", "engines": { From 943ca5ff8781a5809dce7cd22f38ed4058e6fd9c Mon Sep 17 00:00:00 2001 From: Jacob Lee Date: Mon, 26 Aug 2024 11:09:33 -0700 Subject: [PATCH 102/344] core[patch]: Adds option for more verbose parsing errors (#6634) --- langchain-core/src/tools/index.ts | 23 ++++++++++--- langchain-core/src/tools/tests/tools.test.ts | 36 ++++++++++++++++++++ 2 files changed, 54 insertions(+), 5 deletions(-) diff --git a/langchain-core/src/tools/index.ts b/langchain-core/src/tools/index.ts index 7146e2f36df1..42ceff0813ff 100644 --- a/langchain-core/src/tools/index.ts +++ b/langchain-core/src/tools/index.ts @@ -46,6 +46,12 @@ export interface ToolParams extends BaseLangChainParams { * @default "content" */ responseFormat?: ResponseFormat; + /** + * Whether to show full details in the thrown parsing errors. + * + * @default false + */ + verboseParsingErrors?: boolean; } /** @@ -121,6 +127,9 @@ export abstract class StructuredTool< returnDirect = false; + // TODO: Make default in 0.3 + verboseParsingErrors = false; + get lc_namespace() { return ["langchain", "tools"]; } @@ -139,6 +148,8 @@ export abstract class StructuredTool< constructor(fields?: ToolParams) { super(fields ?? {}); + this.verboseParsingErrors = + fields?.verboseParsingErrors ?? this.verboseParsingErrors; this.responseFormat = fields?.responseFormat ?? this.responseFormat; } @@ -205,11 +216,13 @@ export abstract class StructuredTool< let parsed; try { parsed = await this.schema.parseAsync(arg); - } catch (e) { - throw new ToolInputParsingException( - `Received tool input did not match expected schema`, - JSON.stringify(arg) - ); + // eslint-disable-next-line @typescript-eslint/no-explicit-any + } catch (e: any) { + let message = `Received tool input did not match expected schema`; + if (this.verboseParsingErrors) { + message = `${message}\nDetails: ${e.message}`; + } + throw new ToolInputParsingException(message, JSON.stringify(arg)); } const config = parseCallbackConfigArg(configArg); diff --git a/langchain-core/src/tools/tests/tools.test.ts b/langchain-core/src/tools/tests/tools.test.ts index 2988eb64d9cf..bdcc068a4d1a 100644 --- a/langchain-core/src/tools/tests/tools.test.ts +++ b/langchain-core/src/tools/tests/tools.test.ts @@ -222,3 +222,39 @@ test("Tool input typing is enforced", async () => { const res3 = await weatherTool3.invoke("blah"); expect(res3).toEqual("Sunny"); }); + +test("Tool can throw detailed errors", async () => { + const weatherSchema = z.object({ + location: z.string(), + }); + + const stringTool = tool( + (input) => { + return JSON.stringify(input); + }, + { + name: "string_tool", + description: "A tool that appends 'a' to the input string", + schema: weatherSchema, + verboseParsingErrors: true, + } + ); + + await expect( + stringTool.invoke({ + // @ts-expect-error Testing parsing errors + location: 8, + }) + ).rejects.toThrow(`Received tool input did not match expected schema +Details: [ + { + "code": "invalid_type", + "expected": "string", + "received": "number", + "path": [ + "location" + ], + "message": "Expected string, received number" + } +]`); +}); From 64c5d345b84e52fa3f67acdfb0a6832934e71614 Mon Sep 17 00:00:00 2001 From: Brace Sproul Date: Mon, 26 Aug 2024 11:16:33 -0700 Subject: [PATCH 103/344] scripts[patch]: Add spam comment filter (#6633) --- .github/workflows/spam-comment-filter.yml | 29 +++++ libs/langchain-scripts/.eslintrc.cjs | 1 - .../bin/filter_spam_comment.js | 1 + libs/langchain-scripts/package.json | 2 + .../src/filter_spam_comment.ts | 51 ++++++++ yarn.lock | 123 ++++++++++++++++++ 6 files changed, 206 insertions(+), 1 deletion(-) create mode 100644 .github/workflows/spam-comment-filter.yml create mode 100755 libs/langchain-scripts/bin/filter_spam_comment.js create mode 100644 libs/langchain-scripts/src/filter_spam_comment.ts diff --git a/.github/workflows/spam-comment-filter.yml b/.github/workflows/spam-comment-filter.yml new file mode 100644 index 000000000000..bb2db6677de6 --- /dev/null +++ b/.github/workflows/spam-comment-filter.yml @@ -0,0 +1,29 @@ +name: Spam Comment Filter + +on: + issue_comment: + types: [created] + pull_request_review_comment: + types: [created] + +jobs: + filter_spam: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Use Node.js 18.x + uses: actions/setup-node@v3 + with: + node-version: 18.x + - name: Install dependencies + run: yarn install --immutable + - name: Build scripts + run: yarn turbo:command build --filter=@langchain/scripts + - name: Run spam detection script + env: + SPAM_COMMENT_GITHUB_TOKEN: ${{ secrets.SPAM_COMMENT_GITHUB_TOKEN }} + COMMENT_JSON: ${{ toJson(github.event.comment) }} + COMMENT_ID: ${{ github.event.comment.id }} + REPO_OWNER: ${{ github.repository_owner }} + REPO_NAME: ${{ github.event.repository.name }} + run: yarn workspace @langchain/scripts filter_spam_comment \ No newline at end of file diff --git a/libs/langchain-scripts/.eslintrc.cjs b/libs/langchain-scripts/.eslintrc.cjs index c91ae3fca91d..9710d04d75c6 100644 --- a/libs/langchain-scripts/.eslintrc.cjs +++ b/libs/langchain-scripts/.eslintrc.cjs @@ -23,7 +23,6 @@ module.exports = { "*.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, diff --git a/libs/langchain-scripts/bin/filter_spam_comment.js b/libs/langchain-scripts/bin/filter_spam_comment.js new file mode 100755 index 000000000000..0da333aee677 --- /dev/null +++ b/libs/langchain-scripts/bin/filter_spam_comment.js @@ -0,0 +1 @@ +import "../dist/filter_spam_comment.js"; diff --git a/libs/langchain-scripts/package.json b/libs/langchain-scripts/package.json index 13e9167a436b..ffea6eb6d6f1 100644 --- a/libs/langchain-scripts/package.json +++ b/libs/langchain-scripts/package.json @@ -14,6 +14,7 @@ }, "homepage": "https://github.com/langchain-ai/langchainjs/tree/main/libs/langchain-scripts/", "bin": { + "filter_spam_comment": "bin/filter_spam_comment.js", "lc_build": "bin/build.js", "notebook_validate": "bin/validate_notebook.js" }, @@ -40,6 +41,7 @@ "author": "LangChain", "license": "MIT", "dependencies": { + "@octokit/rest": "^21.0.2", "@rollup/wasm-node": "^4.19.0", "axios": "^1.6.7", "commander": "^11.1.0", diff --git a/libs/langchain-scripts/src/filter_spam_comment.ts b/libs/langchain-scripts/src/filter_spam_comment.ts new file mode 100644 index 000000000000..2a7f9105bd6e --- /dev/null +++ b/libs/langchain-scripts/src/filter_spam_comment.ts @@ -0,0 +1,51 @@ +import { Octokit } from "@octokit/rest"; + +async function spamContentFilter() { + if (process.env.SPAM_COMMENT_GITHUB_TOKEN === undefined) { + throw new Error("SPAM_COMMENT_GITHUB_TOKEN is not set"); + } + if (process.env.COMMENT_JSON === undefined) { + throw new Error("COMMENT_JSON is not set"); + } + if (process.env.COMMENT_ID === undefined) { + throw new Error("COMMENT_ID is not set"); + } + if (process.env.REPO_OWNER === undefined) { + throw new Error("REPO_OWNER is not set"); + } + if (process.env.REPO_NAME === undefined) { + throw new Error("REPO_NAME is not set"); + } + + const octokit = new Octokit({ auth: process.env.SPAM_COMMENT_GITHUB_TOKEN }); + + const comment: { body: string } = JSON.parse(process.env.COMMENT_JSON || ""); + const commentId = parseInt(process.env.COMMENT_ID || "", 10); + const owner = process.env.REPO_OWNER || ""; + const repo = process.env.REPO_NAME || ""; + + const SPAM_COMMENT_REGEX = [ + /^download\s+(?:https?:\/\/)?[\w-]+(\.[\w-]+)+[^\s]+\s+password:\s*.+\s+in the installer menu, select\s*.+$/i, + ]; + + if ( + SPAM_COMMENT_REGEX.some((pattern) => + pattern.test(comment.body.toLowerCase()) + ) + ) { + try { + await octokit.rest.issues.deleteComment({ + owner, + repo, + comment_id: commentId, + }); + console.log(`Deleted spam comment with ID: ${commentId}`); + } catch (error) { + console.error("Error deleting comment:", error); + } + } else { + console.log("Comment is not spam"); + } +} + +void spamContentFilter(); diff --git a/yarn.lock b/yarn.lock index 2548d54d63d0..48c3049c4726 100644 --- a/yarn.lock +++ b/yarn.lock @@ -12407,6 +12407,7 @@ __metadata: resolution: "@langchain/scripts@workspace:libs/langchain-scripts" dependencies: "@jest/globals": ^29.5.0 + "@octokit/rest": ^21.0.2 "@rollup/wasm-node": ^4.19.0 "@swc/core": ^1.3.90 "@swc/jest": ^0.2.29 @@ -12438,6 +12439,7 @@ __metadata: tsx: ^4.16.2 typescript: ^5.4.5 bin: + filter_spam_comment: bin/filter_spam_comment.js lc_build: bin/build.js notebook_validate: bin/validate_notebook.js languageName: unknown @@ -13096,6 +13098,13 @@ __metadata: languageName: node linkType: hard +"@octokit/auth-token@npm:^5.0.0": + version: 5.1.1 + resolution: "@octokit/auth-token@npm:5.1.1" + checksum: b39516dda44aeced0326227c53aade621effe1d59c4b0f48ebe2b9fd32b5156e02705bcb2fb1bf48b11f26cc6aff1a0683c32c3d5424e0118dae6596e431d489 + languageName: node + linkType: hard + "@octokit/auth-unauthenticated@npm:^5.0.0": version: 5.0.0 resolution: "@octokit/auth-unauthenticated@npm:5.0.0" @@ -13151,6 +13160,31 @@ __metadata: languageName: node linkType: hard +"@octokit/core@npm:^6.1.2": + version: 6.1.2 + resolution: "@octokit/core@npm:6.1.2" + dependencies: + "@octokit/auth-token": ^5.0.0 + "@octokit/graphql": ^8.0.0 + "@octokit/request": ^9.0.0 + "@octokit/request-error": ^6.0.1 + "@octokit/types": ^13.0.0 + before-after-hook: ^3.0.2 + universal-user-agent: ^7.0.0 + checksum: e794fb11b3942f55033f4cf6c0914953fd974587309498e8709c428660fa5c098334d83af5e41457dbe67d92d70a8b559c6cc00457d6c95290fa6c9e1d4bfc42 + languageName: node + linkType: hard + +"@octokit/endpoint@npm:^10.0.0": + version: 10.1.1 + resolution: "@octokit/endpoint@npm:10.1.1" + dependencies: + "@octokit/types": ^13.0.0 + universal-user-agent: ^7.0.2 + checksum: fde158f40dc9a88e92a8ac1d347a54599aa5715ec24045be9cb8ff8decb3c17b63c91eca1bab12dfe0e0cd37433127dd05cd05db14a719dca749bc56093aa915 + languageName: node + linkType: hard + "@octokit/endpoint@npm:^7.0.0": version: 7.0.6 resolution: "@octokit/endpoint@npm:7.0.6" @@ -13216,6 +13250,17 @@ __metadata: languageName: node linkType: hard +"@octokit/graphql@npm:^8.0.0": + version: 8.1.1 + resolution: "@octokit/graphql@npm:8.1.1" + dependencies: + "@octokit/request": ^9.0.0 + "@octokit/types": ^13.0.0 + universal-user-agent: ^7.0.0 + checksum: 07239666b0ca38a7d8c581570b544ee9fd1a2616c8dd436af31879662b3345c44ed52e3d7b311840a1c5772a23f02caf7585aca56f36e50f38f0207a87577a9c + languageName: node + linkType: hard + "@octokit/oauth-app@npm:^6.0.0": version: 6.0.0 resolution: "@octokit/oauth-app@npm:6.0.0" @@ -13286,6 +13331,17 @@ __metadata: languageName: node linkType: hard +"@octokit/plugin-paginate-rest@npm:^11.0.0": + version: 11.3.3 + resolution: "@octokit/plugin-paginate-rest@npm:11.3.3" + dependencies: + "@octokit/types": ^13.5.0 + peerDependencies: + "@octokit/core": ">=6" + checksum: 93c7993562caed67b67f75aa77ffb10d032c242a70e9380e2fb9ab67dd2fb84d420231d09cd8a64f1553ffd325f3ef8c640c62e4267b7f3b352b16d4d5e11ef6 + languageName: node + linkType: hard + "@octokit/plugin-paginate-rest@npm:^6.1.2": version: 6.1.2 resolution: "@octokit/plugin-paginate-rest@npm:6.1.2" @@ -13327,6 +13383,15 @@ __metadata: languageName: node linkType: hard +"@octokit/plugin-request-log@npm:^5.3.1": + version: 5.3.1 + resolution: "@octokit/plugin-request-log@npm:5.3.1" + peerDependencies: + "@octokit/core": ">=6" + checksum: a27e163282c8d0ba8feee4d3cbbd1b62e1aa89a892877f7a9876fc17ddde3e1e1af922e6664221a0cabae99b8a7a2a5215b9ec2ee5222edb50e06298e99022b0 + languageName: node + linkType: hard + "@octokit/plugin-rest-endpoint-methods@npm:13.2.2": version: 13.2.2 resolution: "@octokit/plugin-rest-endpoint-methods@npm:13.2.2" @@ -13338,6 +13403,17 @@ __metadata: languageName: node linkType: hard +"@octokit/plugin-rest-endpoint-methods@npm:^13.0.0": + version: 13.2.4 + resolution: "@octokit/plugin-rest-endpoint-methods@npm:13.2.4" + dependencies: + "@octokit/types": ^13.5.0 + peerDependencies: + "@octokit/core": ">=6" + checksum: 149643bf98933af92003c55ad7f1e87c239941e843708cfc7389d378e85069e88b7cccaf8227469ee037d54da93cbdb881a34ce9888f5a60f89c689305eb5730 + languageName: node + linkType: hard + "@octokit/plugin-rest-endpoint-methods@npm:^7.1.2": version: 7.2.3 resolution: "@octokit/plugin-rest-endpoint-methods@npm:7.2.3" @@ -13418,6 +13494,15 @@ __metadata: languageName: node linkType: hard +"@octokit/request-error@npm:^6.0.1": + version: 6.1.4 + resolution: "@octokit/request-error@npm:6.1.4" + dependencies: + "@octokit/types": ^13.0.0 + checksum: e4e475ec50cef8e271f39e69667d0f8eaccb2367aa56b81638c629b5bbfa2b697b40207301e5c797a63051a82d8698e7c792b4050b84e383c54300a49a01304a + languageName: node + linkType: hard + "@octokit/request@npm:^6.0.0": version: 6.2.8 resolution: "@octokit/request@npm:6.2.8" @@ -13457,6 +13542,18 @@ __metadata: languageName: node linkType: hard +"@octokit/request@npm:^9.0.0": + version: 9.1.3 + resolution: "@octokit/request@npm:9.1.3" + dependencies: + "@octokit/endpoint": ^10.0.0 + "@octokit/request-error": ^6.0.1 + "@octokit/types": ^13.1.0 + universal-user-agent: ^7.0.2 + checksum: 0a1c1a4f9ba67954402ef6d1e3d8e78518487750f3a31c100133840fff393ed9cc29533282914adf0731f7cc880a2778b8a6ac81527b376a278360a86e79597d + languageName: node + linkType: hard + "@octokit/rest@npm:19.0.11": version: 19.0.11 resolution: "@octokit/rest@npm:19.0.11" @@ -13481,6 +13578,18 @@ __metadata: languageName: node linkType: hard +"@octokit/rest@npm:^21.0.2": + version: 21.0.2 + resolution: "@octokit/rest@npm:21.0.2" + dependencies: + "@octokit/core": ^6.1.2 + "@octokit/plugin-paginate-rest": ^11.0.0 + "@octokit/plugin-request-log": ^5.3.1 + "@octokit/plugin-rest-endpoint-methods": ^13.0.0 + checksum: 81dc98bbc27d4891a211628ea49ba40f087f986ee85d7e2f0579b66e4046dd6b6d63ffeb0eb011c9240dd61906798795e4b9e309af230f31df0a42db79ae20bc + languageName: node + linkType: hard + "@octokit/tsconfig@npm:^1.0.2": version: 1.0.2 resolution: "@octokit/tsconfig@npm:1.0.2" @@ -21311,6 +21420,13 @@ __metadata: languageName: node linkType: hard +"before-after-hook@npm:^3.0.2": + version: 3.0.2 + resolution: "before-after-hook@npm:3.0.2" + checksum: 5f76a9d31909f7f1f7125b7e017ff018799308f5c1fc5a5bfeba9986149da77e6a5cdde0d151671cf374a7fa6452533237bb1de62dfd6c235c20e7c61cc9569d + languageName: node + linkType: hard + "better-sqlite3@npm:9.4.0": version: 9.4.0 resolution: "better-sqlite3@npm:9.4.0" @@ -41774,6 +41890,13 @@ __metadata: languageName: node linkType: hard +"universal-user-agent@npm:^7.0.0, universal-user-agent@npm:^7.0.2": + version: 7.0.2 + resolution: "universal-user-agent@npm:7.0.2" + checksum: 3f02cb6de0bb9fbaf379566bd0320d8e46af6e4358a2e88fce7e70687ed7b48b37f479d728bb22f4204a518e363f3038ac4841c033af1ee2253f6428a6c67e53 + languageName: node + linkType: hard + "universalify@npm:^0.2.0": version: 0.2.0 resolution: "universalify@npm:0.2.0" From 0b34933609088dc331f6bf6be04871bcb052c978 Mon Sep 17 00:00:00 2001 From: Jacob Lee Date: Mon, 26 Aug 2024 11:22:55 -0700 Subject: [PATCH 104/344] core[patch]: Release 0.2.29 (#6635) --- 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 0f1ee3b0bbcb..d59b41eccb20 100644 --- a/langchain-core/package.json +++ b/langchain-core/package.json @@ -1,6 +1,6 @@ { "name": "@langchain/core", - "version": "0.2.28", + "version": "0.2.29", "description": "Core LangChain.js abstractions and schemas", "type": "module", "engines": { From 289ec3554a8dae8b263dd437c3c8342a97e43c51 Mon Sep 17 00:00:00 2001 From: Aditya Kumar <75817213+obround@users.noreply.github.com> Date: Mon, 26 Aug 2024 11:23:37 -0700 Subject: [PATCH 105/344] langchain[patch]: Fixes bug in ReAct agent that prevents parsing error handling (#6624) Co-authored-by: Jacob Lee --- langchain/src/agents/react/output_parser.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/langchain/src/agents/react/output_parser.ts b/langchain/src/agents/react/output_parser.ts index ba8f7b85ce59..3759830b8938 100644 --- a/langchain/src/agents/react/output_parser.ts +++ b/langchain/src/agents/react/output_parser.ts @@ -1,5 +1,6 @@ import { AgentAction, AgentFinish } from "@langchain/core/agents"; import { renderTemplate } from "@langchain/core/prompts"; +import { OutputParserException } from "@langchain/core/output_parsers"; import { AgentActionOutputParser } from "../types.js"; import { FORMAT_INSTRUCTIONS } from "./prompt.js"; @@ -69,7 +70,7 @@ export class ReActSingleInputOutputParser extends AgentActionOutputParser { const actionMatch = text.match(regex); if (actionMatch) { if (includesAnswer) { - throw new Error( + throw new OutputParserException( `${FINAL_ANSWER_AND_PARSABLE_ACTION_ERROR_MESSAGE}: ${text}` ); } @@ -95,7 +96,7 @@ export class ReActSingleInputOutputParser extends AgentActionOutputParser { }; } - throw new Error(`Could not parse LLM output: ${text}`); + throw new OutputParserException(`Could not parse LLM output: ${text}`); } /** From 3371d5dbb978a80e99694368a83e3625995b3a41 Mon Sep 17 00:00:00 2001 From: Brace Sproul Date: Mon, 26 Aug 2024 11:37:50 -0700 Subject: [PATCH 106/344] infra[patch]: Improve speeds for spam detection script (#6636) --- .github/workflows/spam-comment-filter.yml | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/.github/workflows/spam-comment-filter.yml b/.github/workflows/spam-comment-filter.yml index bb2db6677de6..b96f08f5896a 100644 --- a/.github/workflows/spam-comment-filter.yml +++ b/.github/workflows/spam-comment-filter.yml @@ -15,15 +15,27 @@ jobs: uses: actions/setup-node@v3 with: node-version: 18.x + - name: Check issue body against regex + id: regex_check + run: | + REGEX='^download\s+(?:https?:\/\/)?[\w-]+(\.[\w-]+)+[^\s]+\s+password:\s*.+\s+in the installer menu, select\s*.+$' + if echo "${{ github.event.comment.body }}" | grep -qiE "$REGEX"; then + echo "REGEX_MATCHED=true" >> $GITHUB_OUTPUT + else + echo "REGEX_MATCHED=false" >> $GITHUB_OUTPUT + fi - name: Install dependencies - run: yarn install --immutable + if: steps.regex_check.outputs.REGEX_MATCHED == 'true' + run: cd ./libs/langchain-scripts && yarn workspaces focus - name: Build scripts - run: yarn turbo:command build --filter=@langchain/scripts + if: steps.regex_check.outputs.REGEX_MATCHED == 'true' + run: cd ./libs/langchain-scripts && yarn build:internal - name: Run spam detection script + if: steps.regex_check.outputs.REGEX_MATCHED == 'true' env: SPAM_COMMENT_GITHUB_TOKEN: ${{ secrets.SPAM_COMMENT_GITHUB_TOKEN }} COMMENT_JSON: ${{ toJson(github.event.comment) }} COMMENT_ID: ${{ github.event.comment.id }} REPO_OWNER: ${{ github.repository_owner }} REPO_NAME: ${{ github.event.repository.name }} - run: yarn workspace @langchain/scripts filter_spam_comment \ No newline at end of file + run: cd ./libs/langchain-scripts && yarn filter_spam_comment \ No newline at end of file From 4f3a8d2fd353c8e3ba25b336d44ff1e9b444a0a8 Mon Sep 17 00:00:00 2001 From: Brace Sproul Date: Mon, 26 Aug 2024 12:20:18 -0700 Subject: [PATCH 107/344] infra[patch]: Fix comment regex (#6637) --- .github/workflows/spam-comment-filter.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/spam-comment-filter.yml b/.github/workflows/spam-comment-filter.yml index b96f08f5896a..ecb632327ac6 100644 --- a/.github/workflows/spam-comment-filter.yml +++ b/.github/workflows/spam-comment-filter.yml @@ -19,7 +19,7 @@ jobs: id: regex_check run: | REGEX='^download\s+(?:https?:\/\/)?[\w-]+(\.[\w-]+)+[^\s]+\s+password:\s*.+\s+in the installer menu, select\s*.+$' - if echo "${{ github.event.comment.body }}" | grep -qiE "$REGEX"; then + if echo "${{ github.event.comment.body }}" | tr '\n' ' ' | grep -qiP "$REGEX"; then echo "REGEX_MATCHED=true" >> $GITHUB_OUTPUT else echo "REGEX_MATCHED=false" >> $GITHUB_OUTPUT From c0a19d00e2743943a3785b2bce48365c5080c769 Mon Sep 17 00:00:00 2001 From: Jacob Lee Date: Mon, 26 Aug 2024 15:44:37 -0700 Subject: [PATCH 108/344] core[patch]: Make tool call chunk partial JSON parsing more strict (#6639) --- langchain-core/src/messages/ai.ts | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/langchain-core/src/messages/ai.ts b/langchain-core/src/messages/ai.ts index 2ba47ff7ff2a..9c542af4e28b 100644 --- a/langchain-core/src/messages/ai.ts +++ b/langchain-core/src/messages/ai.ts @@ -188,8 +188,12 @@ export class AIMessageChunk extends BaseMessageChunk { for (const toolCallChunk of fields.tool_call_chunks) { let parsedArgs = {}; try { - parsedArgs = parsePartialJson(toolCallChunk.args ?? "{}") ?? {}; - if (typeof parsedArgs !== "object" || Array.isArray(parsedArgs)) { + parsedArgs = parsePartialJson(toolCallChunk.args || "{}"); + if ( + parsedArgs === null || + typeof parsedArgs !== "object" || + Array.isArray(parsedArgs) + ) { throw new Error("Malformed tool call chunk args."); } toolCalls.push({ From 3d8917b558c200f69f9dbd23932023fb5be53d80 Mon Sep 17 00:00:00 2001 From: Jacob Lee Date: Mon, 26 Aug 2024 15:44:46 -0700 Subject: [PATCH 109/344] openai[patch]: Properly dedupe system fingerprint (#6638) --- libs/langchain-openai/src/chat_models.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libs/langchain-openai/src/chat_models.ts b/libs/langchain-openai/src/chat_models.ts index 0a93c9d1b138..cedb15777527 100644 --- a/libs/langchain-openai/src/chat_models.ts +++ b/libs/langchain-openai/src/chat_models.ts @@ -1255,7 +1255,7 @@ export class ChatOpenAI< } // eslint-disable-next-line @typescript-eslint/no-explicit-any const generationInfo: Record = { ...newTokenIndices }; - if (choice.finish_reason !== undefined) { + if (choice.finish_reason != null) { generationInfo.finish_reason = choice.finish_reason; // Only include system fingerprint in the last chunk for now // to avoid concatenation issues From e3e014cff6cdd29220158d793566dc724595c0c3 Mon Sep 17 00:00:00 2001 From: Jacob Lee Date: Mon, 26 Aug 2024 15:54:19 -0700 Subject: [PATCH 110/344] openai[patch]: Release 0.2.8 (#6640) --- libs/langchain-openai/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libs/langchain-openai/package.json b/libs/langchain-openai/package.json index b6fd72c247cc..1a7bc2f35490 100644 --- a/libs/langchain-openai/package.json +++ b/libs/langchain-openai/package.json @@ -1,6 +1,6 @@ { "name": "@langchain/openai", - "version": "0.2.7", + "version": "0.2.8", "description": "OpenAI integrations for LangChain.js", "type": "module", "engines": { From 608bbf12ba5bf039d6cc966d40cc2deb2ebcdd87 Mon Sep 17 00:00:00 2001 From: Ben Burns <803016+benjamincburns@users.noreply.github.com> Date: Tue, 27 Aug 2024 10:59:14 +1200 Subject: [PATCH 111/344] core[patch]: Prevent ConsoleCallbackHandler from throwing TypeError when logging (#6612) Co-authored-by: jacoblee93 --- langchain-core/src/tracers/console.ts | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/langchain-core/src/tracers/console.ts b/langchain-core/src/tracers/console.ts index 00ea5e10e4e9..01e0540a8e06 100644 --- a/langchain-core/src/tracers/console.ts +++ b/langchain-core/src/tracers/console.ts @@ -14,6 +14,18 @@ function tryJsonStringify(obj: unknown, fallback: string) { } } +function formatKVMapItem(value: unknown) { + if (typeof value === "string") { + return value.trim(); + } + + if (value === null || value === undefined) { + return value; + } + + return tryJsonStringify(value, value.toString()); +} + function elapsed(run: Run): string { if (!run.end_time) return ""; const elapsed = run.end_time - run.start_time; @@ -210,7 +222,9 @@ export class ConsoleCallbackHandler extends BaseTracer { `${wrap( color.green, "[tool/start]" - )} [${crumbs}] Entering Tool run with input: "${run.inputs.input?.trim()}"` + )} [${crumbs}] Entering Tool run with input: "${formatKVMapItem( + run.inputs.input + )}"` ); } @@ -221,10 +235,13 @@ export class ConsoleCallbackHandler extends BaseTracer { */ onToolEnd(run: Run) { const crumbs = this.getBreadcrumbs(run); + console.log( `${wrap(color.cyan, "[tool/end]")} [${crumbs}] [${elapsed( run - )}] Exiting Tool run with output: "${run.outputs?.output?.trim()}"` + )}] Exiting Tool run with output: "${formatKVMapItem( + run.outputs?.output + )}"` ); } From 66749d5f5fd1417d3778587716044b91fd22e105 Mon Sep 17 00:00:00 2001 From: Jacob Lee Date: Mon, 26 Aug 2024 16:11:00 -0700 Subject: [PATCH 112/344] core[patch]: Release 0.2.30 (#6641) --- 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 d59b41eccb20..cc1de8205949 100644 --- a/langchain-core/package.json +++ b/langchain-core/package.json @@ -1,6 +1,6 @@ { "name": "@langchain/core", - "version": "0.2.29", + "version": "0.2.30", "description": "Core LangChain.js abstractions and schemas", "type": "module", "engines": { From 5e9323642ac9dd9308ecab0cce980d9451fb2b1a Mon Sep 17 00:00:00 2001 From: Adnan Khan Date: Tue, 27 Aug 2024 19:16:53 -0400 Subject: [PATCH 113/344] infra[patch]: Sanitize body in spam filter (#6642) --- .github/workflows/spam-comment-filter.yml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/.github/workflows/spam-comment-filter.yml b/.github/workflows/spam-comment-filter.yml index ecb632327ac6..4f974e6e9941 100644 --- a/.github/workflows/spam-comment-filter.yml +++ b/.github/workflows/spam-comment-filter.yml @@ -17,9 +17,11 @@ jobs: node-version: 18.x - name: Check issue body against regex id: regex_check + env: + COMMENT_BODY: ${{ github.event.comment.body }} run: | REGEX='^download\s+(?:https?:\/\/)?[\w-]+(\.[\w-]+)+[^\s]+\s+password:\s*.+\s+in the installer menu, select\s*.+$' - if echo "${{ github.event.comment.body }}" | tr '\n' ' ' | grep -qiP "$REGEX"; then + if echo "$COMMENT_BODY" | tr '\n' ' ' | grep -qiP "$REGEX"; then echo "REGEX_MATCHED=true" >> $GITHUB_OUTPUT else echo "REGEX_MATCHED=false" >> $GITHUB_OUTPUT @@ -38,4 +40,4 @@ jobs: COMMENT_ID: ${{ github.event.comment.id }} REPO_OWNER: ${{ github.repository_owner }} REPO_NAME: ${{ github.event.repository.name }} - run: cd ./libs/langchain-scripts && yarn filter_spam_comment \ No newline at end of file + run: cd ./libs/langchain-scripts && yarn filter_spam_comment From b9193b2feb0a3397c641380940d515cf56cda0c8 Mon Sep 17 00:00:00 2001 From: Brace Sproul Date: Tue, 27 Aug 2024 17:40:53 -0700 Subject: [PATCH 114/344] docs[patch]: Update ParentDocument docs to use byte store (#6647) --- examples/src/retrievers/parent_document_retriever.ts | 7 ++++--- .../retrievers/parent_document_retriever_chunk_header.ts | 4 ++-- .../src/retrievers/parent_document_retriever_rerank.ts | 4 ++-- .../parent_document_retriever_score_threshold.ts | 5 +++-- 4 files changed, 11 insertions(+), 9 deletions(-) diff --git a/examples/src/retrievers/parent_document_retriever.ts b/examples/src/retrievers/parent_document_retriever.ts index 7112ce797cff..530b9b5a8cb7 100644 --- a/examples/src/retrievers/parent_document_retriever.ts +++ b/examples/src/retrievers/parent_document_retriever.ts @@ -1,15 +1,16 @@ import { OpenAIEmbeddings } from "@langchain/openai"; import { MemoryVectorStore } from "langchain/vectorstores/memory"; -import { InMemoryStore } from "@langchain/core/stores"; import { ParentDocumentRetriever } from "langchain/retrievers/parent_document"; import { RecursiveCharacterTextSplitter } from "@langchain/textsplitters"; import { TextLoader } from "langchain/document_loaders/fs/text"; +import { InMemoryStore } from "@langchain/core/stores"; const vectorstore = new MemoryVectorStore(new OpenAIEmbeddings()); -const docstore = new InMemoryStore(); +const byteStore = new InMemoryStore(); + const retriever = new ParentDocumentRetriever({ vectorstore, - docstore, + byteStore, // Optional, not required if you're already passing in split documents parentSplitter: new RecursiveCharacterTextSplitter({ chunkOverlap: 0, diff --git a/examples/src/retrievers/parent_document_retriever_chunk_header.ts b/examples/src/retrievers/parent_document_retriever_chunk_header.ts index 9dbdbb01ddc7..443aed45712e 100644 --- a/examples/src/retrievers/parent_document_retriever_chunk_header.ts +++ b/examples/src/retrievers/parent_document_retriever_chunk_header.ts @@ -22,11 +22,11 @@ const pamChunkHeaderOptions = { }; const vectorstore = await HNSWLib.fromDocuments([], new OpenAIEmbeddings()); -const docstore = new InMemoryStore(); +const byteStore = new InMemoryStore(); const retriever = new ParentDocumentRetriever({ vectorstore, - docstore, + byteStore, // Very small chunks for demo purposes. // Use a bigger chunk size for serious use-cases. childSplitter: new RecursiveCharacterTextSplitter({ diff --git a/examples/src/retrievers/parent_document_retriever_rerank.ts b/examples/src/retrievers/parent_document_retriever_rerank.ts index f0918b602a67..e6c008d450ad 100644 --- a/examples/src/retrievers/parent_document_retriever_rerank.ts +++ b/examples/src/retrievers/parent_document_retriever_rerank.ts @@ -44,11 +44,11 @@ const jimDocs = await splitter.createDocuments([`Jim favorite color is blue.`]); const pamDocs = await splitter.createDocuments([`Pam favorite color is red.`]); const vectorstore = await HNSWLib.fromDocuments([], new OpenAIEmbeddings()); -const docstore = new InMemoryStore(); +const byteStore = new InMemoryStore(); const retriever = new ParentDocumentRetriever({ vectorstore, - docstore, + byteStore, // Very small chunks for demo purposes. // Use a bigger chunk size for serious use-cases. childSplitter: new RecursiveCharacterTextSplitter({ diff --git a/examples/src/retrievers/parent_document_retriever_score_threshold.ts b/examples/src/retrievers/parent_document_retriever_score_threshold.ts index c5ad4aeda050..aa2c616e8a15 100644 --- a/examples/src/retrievers/parent_document_retriever_score_threshold.ts +++ b/examples/src/retrievers/parent_document_retriever_score_threshold.ts @@ -7,7 +7,8 @@ import { TextLoader } from "langchain/document_loaders/fs/text"; import { ScoreThresholdRetriever } from "langchain/retrievers/score_threshold"; const vectorstore = new MemoryVectorStore(new OpenAIEmbeddings()); -const docstore = new InMemoryStore(); +const byteStore = new InMemoryStore(); + const childDocumentRetriever = ScoreThresholdRetriever.fromVectorStore( vectorstore, { @@ -17,7 +18,7 @@ const childDocumentRetriever = ScoreThresholdRetriever.fromVectorStore( ); const retriever = new ParentDocumentRetriever({ vectorstore, - docstore, + byteStore, childDocumentRetriever, // Optional, not required if you're already passing in split documents parentSplitter: new RecursiveCharacterTextSplitter({ From bf6e7833b94649b03af67a4d1f0f75041897120a Mon Sep 17 00:00:00 2001 From: Tim Susa Date: Wed, 28 Aug 2024 17:56:32 +0200 Subject: [PATCH 115/344] docs[patch]: Update migrate_agent.ipynb -> was refering to the wrong doc link (#6651) --- docs/core_docs/docs/how_to/migrate_agent.ipynb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/core_docs/docs/how_to/migrate_agent.ipynb b/docs/core_docs/docs/how_to/migrate_agent.ipynb index 32468784478a..e4cb32b8ad30 100644 --- a/docs/core_docs/docs/how_to/migrate_agent.ipynb +++ b/docs/core_docs/docs/how_to/migrate_agent.ipynb @@ -1643,7 +1643,7 @@ "\n", "You've now learned how to migrate your LangChain agent executors to LangGraph.\n", "\n", - "Next, check out other [LangGraph how-to guides](https://langchain-ai.github.io/langgraph/how-tos/)." + "Next, check out other [LangGraph how-to guides](https://langchain-ai.github.io/langgraphjs/how-tos/)." ] }, { From dae691c0c717b61feae42284b01b1adf57a93dd7 Mon Sep 17 00:00:00 2001 From: Kevin Simons Date: Wed, 28 Aug 2024 18:33:28 +0200 Subject: [PATCH 116/344] Fix typo in agents tutorial (#6649) --- docs/core_docs/docs/tutorials/agents.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/core_docs/docs/tutorials/agents.mdx b/docs/core_docs/docs/tutorials/agents.mdx index 6176affc1a07..201339008d9d 100644 --- a/docs/core_docs/docs/tutorials/agents.mdx +++ b/docs/core_docs/docs/tutorials/agents.mdx @@ -16,7 +16,7 @@ This guide assumes familiarity with the following concepts: By themselves, language models can't take actions - they just output text. A big use case for LangChain is creating **agents**. -Agents are systems that use an LLM as a reasoning enginer to determine which actions to take and what the inputs to those actions should be. +Agents are systems that use an LLM as a reasoning engine to determine which actions to take and what the inputs to those actions should be. The results of those actions can then be fed back into the agent and it determine whether more actions are needed, or whether it is okay to finish. In this tutorial we will build an agent that can interact with multiple different tools: one being a local database, the other being a search engine. You will be able to ask this agent questions, watch it call tools, and have conversations with it. From 0c003d8d602cf7ac396053ac59b22deb2b95f1bb Mon Sep 17 00:00:00 2001 From: David Duong Date: Wed, 28 Aug 2024 20:24:00 +0200 Subject: [PATCH 117/344] feat(core): always provide message ID inferred from run ID if not present while streaming (#6646) --- .../src/language_models/chat_models.ts | 12 +++ langchain-core/src/messages/base.ts | 10 ++ .../runnables/tests/runnable_history.test.ts | 23 ++++- .../tests/runnable_stream_events.test.ts | 22 +++-- .../tests/runnable_stream_events_v2.test.ts | 97 +++++++++++++------ langchain-core/src/tracers/event_stream.ts | 2 +- langchain-core/src/tracers/log_stream.ts | 5 +- 7 files changed, 124 insertions(+), 47 deletions(-) diff --git a/langchain-core/src/language_models/chat_models.ts b/langchain-core/src/language_models/chat_models.ts index 9239ff52c03b..a80776ff0ba3 100644 --- a/langchain-core/src/language_models/chat_models.ts +++ b/langchain-core/src/language_models/chat_models.ts @@ -264,6 +264,10 @@ export abstract class BaseChatModel< callOptions, runManagers?.[0] )) { + if (chunk.message.id == null) { + const runId = runManagers?.at(0)?.runId; + if (runId != null) chunk.message._updateId(`run-${runId}`); + } chunk.message.response_metadata = { ...chunk.generationInfo, ...chunk.message.response_metadata, @@ -362,6 +366,10 @@ export abstract class BaseChatModel< ); let aggregated; for await (const chunk of stream) { + if (chunk.message.id == null) { + const runId = runManagers?.at(0)?.runId; + if (runId != null) chunk.message._updateId(`run-${runId}`); + } if (aggregated === undefined) { aggregated = chunk; } else { @@ -397,6 +405,10 @@ export abstract class BaseChatModel< if (pResult.status === "fulfilled") { const result = pResult.value; for (const generation of result.generations) { + if (generation.message.id == null) { + const runId = runManagers?.at(0)?.runId; + if (runId != null) generation.message._updateId(`run-${runId}`); + } generation.message.response_metadata = { ...generation.generationInfo, ...generation.message.response_metadata, diff --git a/langchain-core/src/messages/base.ts b/langchain-core/src/messages/base.ts index 8b74c7f158e1..639fcf226025 100644 --- a/langchain-core/src/messages/base.ts +++ b/langchain-core/src/messages/base.ts @@ -289,6 +289,16 @@ export abstract class BaseMessage }; } + // this private method is used to update the ID for the runtime + // value as well as in lc_kwargs for serialisation + _updateId(value: string | undefined) { + this.id = value; + + // lc_attributes wouldn't work here, because jest compares the + // whole object + this.lc_kwargs.id = value; + } + get [Symbol.toStringTag]() { // eslint-disable-next-line @typescript-eslint/no-explicit-any return (this.constructor as any).lc_name(); diff --git a/langchain-core/src/runnables/tests/runnable_history.test.ts b/langchain-core/src/runnables/tests/runnable_history.test.ts index 4efe19047e7a..0614f35c71ce 100644 --- a/langchain-core/src/runnables/tests/runnable_history.test.ts +++ b/langchain-core/src/runnables/tests/runnable_history.test.ts @@ -22,6 +22,8 @@ import { import { ChatPromptTemplate, MessagesPlaceholder } from "../../prompts/chat.js"; import { StringOutputParser } from "../../output_parsers/string.js"; +const anyString = expect.any(String) as unknown as string; + // For `BaseChatMessageHistory` async function getGetSessionHistory(): Promise< (sessionId: string) => Promise @@ -107,9 +109,15 @@ test("Runnable with message history with a chat model", async () => { const sessionHistory = await getMessageHistory("2"); expect(await sessionHistory.getMessages()).toEqual([ new HumanMessage("hello"), - new AIMessage("Hello world!"), + new AIMessage({ + id: anyString, + content: "Hello world!", + }), new HumanMessage("good bye"), - new AIMessageChunk("Hello world!"), + new AIMessageChunk({ + id: anyString, + content: "Hello world!", + }), ]); }); @@ -129,6 +137,7 @@ test("Runnable with message history with a messages in, messages out chain", asy config: {}, getMessageHistory, }); + const config: RunnableConfig = { configurable: { sessionId: "2" } }; const output = await withHistory.invoke([new HumanMessage("hello")], config); expect(output.content).toBe("So long and thanks for the fish!!"); @@ -147,9 +156,15 @@ test("Runnable with message history with a messages in, messages out chain", asy const sessionHistory = await getMessageHistory("2"); expect(await sessionHistory.getMessages()).toEqual([ new HumanMessage("hello"), - new AIMessage("So long and thanks for the fish!!"), + new AIMessage({ + id: anyString, + content: "So long and thanks for the fish!!", + }), new HumanMessage("good bye"), - new AIMessageChunk("So long and thanks for the fish!!"), + new AIMessageChunk({ + id: anyString, + content: "So long and thanks for the fish!!", + }), ]); }); diff --git a/langchain-core/src/runnables/tests/runnable_stream_events.test.ts b/langchain-core/src/runnables/tests/runnable_stream_events.test.ts index e9ab07a36fae..7f6bce337ce8 100644 --- a/langchain-core/src/runnables/tests/runnable_stream_events.test.ts +++ b/langchain-core/src/runnables/tests/runnable_stream_events.test.ts @@ -30,6 +30,8 @@ function reverse(s: string) { return s.split("").reverse().join(""); } +const anyString = expect.any(String) as unknown as string; + const originalCallbackValue = process.env.LANGCHAIN_CALLBACKS_BACKGROUND; afterEach(() => { @@ -733,7 +735,7 @@ test("Runnable streamEvents method with chat model chain", async () => { ls_stop: undefined, }, name: "my_model", - data: { chunk: new AIMessageChunk("R") }, + data: { chunk: new AIMessageChunk({ id: anyString, content: "R" }) }, }, { event: "on_chain_stream", @@ -743,7 +745,7 @@ test("Runnable streamEvents method with chat model chain", async () => { foo: "bar", }, name: "my_chain", - data: { chunk: new AIMessageChunk("R") }, + data: { chunk: new AIMessageChunk({ id: anyString, content: "R" }) }, }, { event: "on_llm_stream", @@ -756,7 +758,7 @@ test("Runnable streamEvents method with chat model chain", async () => { ls_stop: undefined, }, name: "my_model", - data: { chunk: new AIMessageChunk("O") }, + data: { chunk: new AIMessageChunk({ id: anyString, content: "O" }) }, }, { event: "on_chain_stream", @@ -766,7 +768,7 @@ test("Runnable streamEvents method with chat model chain", async () => { foo: "bar", }, name: "my_chain", - data: { chunk: new AIMessageChunk("O") }, + data: { chunk: new AIMessageChunk({ id: anyString, content: "O" }) }, }, { event: "on_llm_stream", @@ -779,7 +781,7 @@ test("Runnable streamEvents method with chat model chain", async () => { ls_stop: undefined, }, name: "my_model", - data: { chunk: new AIMessageChunk("A") }, + data: { chunk: new AIMessageChunk({ id: anyString, content: "A" }) }, }, { event: "on_chain_stream", @@ -789,7 +791,7 @@ test("Runnable streamEvents method with chat model chain", async () => { foo: "bar", }, name: "my_chain", - data: { chunk: new AIMessageChunk("A") }, + data: { chunk: new AIMessageChunk({ id: anyString, content: "A" }) }, }, { event: "on_llm_stream", @@ -802,7 +804,7 @@ test("Runnable streamEvents method with chat model chain", async () => { ls_stop: undefined, }, name: "my_model", - data: { chunk: new AIMessageChunk("R") }, + data: { chunk: new AIMessageChunk({ id: anyString, content: "R" }) }, }, { event: "on_chain_stream", @@ -812,7 +814,7 @@ test("Runnable streamEvents method with chat model chain", async () => { foo: "bar", }, name: "my_chain", - data: { chunk: new AIMessageChunk("R") }, + data: { chunk: new AIMessageChunk({ id: anyString, content: "R" }) }, }, { event: "on_llm_end", @@ -836,7 +838,7 @@ test("Runnable streamEvents method with chat model chain", async () => { [ new ChatGenerationChunk({ generationInfo: {}, - message: new AIMessageChunk("ROAR"), + message: new AIMessageChunk({ id: anyString, content: "ROAR" }), text: "ROAR", }), ], @@ -853,7 +855,7 @@ test("Runnable streamEvents method with chat model chain", async () => { foo: "bar", }, data: { - output: new AIMessageChunk("ROAR"), + output: new AIMessageChunk({ id: anyString, content: "ROAR" }), }, }, ]); diff --git a/langchain-core/src/runnables/tests/runnable_stream_events_v2.test.ts b/langchain-core/src/runnables/tests/runnable_stream_events_v2.test.ts index d1fc1ea7fc65..1702d226aa4b 100644 --- a/langchain-core/src/runnables/tests/runnable_stream_events_v2.test.ts +++ b/langchain-core/src/runnables/tests/runnable_stream_events_v2.test.ts @@ -90,6 +90,10 @@ test("Runnable streamEvents method on a chat model", async () => { for await (const event of eventStream) { events.push(event); } + + // used here to avoid casting every ID + const anyString = expect.any(String) as unknown as string; + expect(events).toMatchObject([ { data: { input: "hello" }, @@ -100,7 +104,7 @@ test("Runnable streamEvents method on a chat model", async () => { tags: [], }, { - data: { chunk: new AIMessageChunk({ content: "a" }) }, + data: { chunk: new AIMessageChunk({ id: anyString, content: "a" }) }, event: "on_chat_model_stream", name: "FakeListChatModel", metadata: expect.any(Object), @@ -108,7 +112,7 @@ test("Runnable streamEvents method on a chat model", async () => { tags: [], }, { - data: { chunk: new AIMessageChunk({ content: "b" }) }, + data: { chunk: new AIMessageChunk({ id: anyString, content: "b" }) }, event: "on_chat_model_stream", name: "FakeListChatModel", metadata: expect.any(Object), @@ -116,7 +120,7 @@ test("Runnable streamEvents method on a chat model", async () => { tags: [], }, { - data: { chunk: new AIMessageChunk({ content: "c" }) }, + data: { chunk: new AIMessageChunk({ id: anyString, content: "c" }) }, event: "on_chat_model_stream", name: "FakeListChatModel", metadata: expect.any(Object), @@ -124,7 +128,7 @@ test("Runnable streamEvents method on a chat model", async () => { tags: [], }, { - data: { output: new AIMessageChunk({ content: "abc" }) }, + data: { output: new AIMessageChunk({ id: anyString, content: "abc" }) }, event: "on_chat_model_end", name: "FakeListChatModel", metadata: expect.any(Object), @@ -748,6 +752,9 @@ test("Runnable streamEvents method with chat model chain", async () => { for await (const event of eventStream) { events.push(event); } + + // used here to avoid casting every ID + const anyString = expect.any(String) as unknown as string; expect(events).toEqual([ { run_id: expect.any(String), @@ -816,7 +823,7 @@ test("Runnable streamEvents method with chat model chain", async () => { ls_stop: undefined, }, name: "my_model", - data: { chunk: new AIMessageChunk("R") }, + data: { chunk: new AIMessageChunk({ content: "R", id: anyString }) }, }, { event: "on_chain_stream", @@ -826,7 +833,7 @@ test("Runnable streamEvents method with chat model chain", async () => { foo: "bar", }, name: "my_chain", - data: { chunk: new AIMessageChunk("R") }, + data: { chunk: new AIMessageChunk({ content: "R", id: anyString }) }, }, { event: "on_chat_model_stream", @@ -839,7 +846,7 @@ test("Runnable streamEvents method with chat model chain", async () => { ls_stop: undefined, }, name: "my_model", - data: { chunk: new AIMessageChunk("O") }, + data: { chunk: new AIMessageChunk({ content: "O", id: anyString }) }, }, { event: "on_chain_stream", @@ -849,7 +856,7 @@ test("Runnable streamEvents method with chat model chain", async () => { foo: "bar", }, name: "my_chain", - data: { chunk: new AIMessageChunk("O") }, + data: { chunk: new AIMessageChunk({ content: "O", id: anyString }) }, }, { event: "on_chat_model_stream", @@ -862,7 +869,7 @@ test("Runnable streamEvents method with chat model chain", async () => { ls_stop: undefined, }, name: "my_model", - data: { chunk: new AIMessageChunk("A") }, + data: { chunk: new AIMessageChunk({ content: "A", id: anyString }) }, }, { event: "on_chain_stream", @@ -872,7 +879,7 @@ test("Runnable streamEvents method with chat model chain", async () => { foo: "bar", }, name: "my_chain", - data: { chunk: new AIMessageChunk("A") }, + data: { chunk: new AIMessageChunk({ content: "A", id: anyString }) }, }, { event: "on_chat_model_stream", @@ -885,7 +892,7 @@ test("Runnable streamEvents method with chat model chain", async () => { ls_stop: undefined, }, name: "my_model", - data: { chunk: new AIMessageChunk("R") }, + data: { chunk: new AIMessageChunk({ content: "R", id: anyString }) }, }, { event: "on_chain_stream", @@ -895,7 +902,7 @@ test("Runnable streamEvents method with chat model chain", async () => { foo: "bar", }, name: "my_chain", - data: { chunk: new AIMessageChunk("R") }, + data: { chunk: new AIMessageChunk({ content: "R", id: anyString }) }, }, { event: "on_chat_model_end", @@ -914,7 +921,7 @@ test("Runnable streamEvents method with chat model chain", async () => { [new SystemMessage("You are Godzilla"), new HumanMessage("hello")], ], }, - output: new AIMessageChunk("ROAR"), + output: new AIMessageChunk({ content: "ROAR", id: anyString }), }, }, { @@ -925,9 +932,7 @@ test("Runnable streamEvents method with chat model chain", async () => { metadata: { foo: "bar", }, - data: { - output: new AIMessageChunk("ROAR"), - }, + data: { output: new AIMessageChunk({ content: "ROAR", id: anyString }) }, }, ]); }); @@ -965,6 +970,10 @@ test("Chat model that supports streaming, but is invoked, should still emit on_s for await (const event of eventStream) { events.push(event); } + + // used here to avoid casting every ID + const anyString = expect.any(String) as unknown as string; + expect(events).toEqual([ { run_id: expect.any(String), @@ -1043,7 +1052,7 @@ test("Chat model that supports streaming, but is invoked, should still emit on_s ls_stop: undefined, }, name: "my_model", - data: { chunk: new AIMessageChunk("R") }, + data: { chunk: new AIMessageChunk({ id: anyString, content: "R" }) }, }, { event: "on_chat_model_stream", @@ -1056,7 +1065,7 @@ test("Chat model that supports streaming, but is invoked, should still emit on_s ls_stop: undefined, }, name: "my_model", - data: { chunk: new AIMessageChunk("O") }, + data: { chunk: new AIMessageChunk({ id: anyString, content: "O" }) }, }, { event: "on_chat_model_stream", @@ -1069,7 +1078,7 @@ test("Chat model that supports streaming, but is invoked, should still emit on_s ls_stop: undefined, }, name: "my_model", - data: { chunk: new AIMessageChunk("A") }, + data: { chunk: new AIMessageChunk({ id: anyString, content: "A" }) }, }, { event: "on_chat_model_stream", @@ -1082,7 +1091,7 @@ test("Chat model that supports streaming, but is invoked, should still emit on_s ls_stop: undefined, }, name: "my_model", - data: { chunk: new AIMessageChunk("R") }, + data: { chunk: new AIMessageChunk({ id: anyString, content: "R" }) }, }, { event: "on_chat_model_end", @@ -1101,7 +1110,7 @@ test("Chat model that supports streaming, but is invoked, should still emit on_s [new SystemMessage("You are Godzilla"), new HumanMessage("hello")], ], }, - output: new AIMessageChunk("ROAR"), + output: new AIMessageChunk({ id: anyString, content: "ROAR" }), }, }, { @@ -1112,7 +1121,7 @@ test("Chat model that supports streaming, but is invoked, should still emit on_s foo: "bar", }, name: "RunnableLambda", - data: { chunk: new AIMessageChunk("ROAR") }, + data: { chunk: new AIMessageChunk({ id: anyString, content: "ROAR" }) }, }, { event: "on_chain_stream", @@ -1122,7 +1131,7 @@ test("Chat model that supports streaming, but is invoked, should still emit on_s foo: "bar", }, name: "my_chain", - data: { chunk: new AIMessageChunk("ROAR") }, + data: { chunk: new AIMessageChunk({ id: anyString, content: "ROAR" }) }, }, { event: "on_chain_end", @@ -1134,7 +1143,7 @@ test("Chat model that supports streaming, but is invoked, should still emit on_s }, data: { input: await template.invoke({ question: "hello" }), - output: new AIMessageChunk("ROAR"), + output: new AIMessageChunk({ id: anyString, content: "ROAR" }), }, }, { @@ -1146,7 +1155,7 @@ test("Chat model that supports streaming, but is invoked, should still emit on_s foo: "bar", }, data: { - output: new AIMessageChunk("ROAR"), + output: new AIMessageChunk({ id: anyString, content: "ROAR" }), }, }, ]); @@ -1183,6 +1192,8 @@ test("Chat model that doesn't support streaming, but is invoked, should emit one for await (const event of eventStream) { events.push(event); } + + const anyString = expect.any(String) as unknown as string; expect(events).toEqual([ { run_id: expect.any(String), @@ -1261,7 +1272,12 @@ test("Chat model that doesn't support streaming, but is invoked, should emit one ls_stop: undefined, }, name: "my_model", - data: { chunk: new AIMessageChunk("You are Godzilla\nhello") }, + data: { + chunk: new AIMessageChunk({ + id: anyString, + content: "You are Godzilla\nhello", + }), + }, }, { event: "on_chat_model_end", @@ -1280,7 +1296,10 @@ test("Chat model that doesn't support streaming, but is invoked, should emit one [new SystemMessage("You are Godzilla"), new HumanMessage("hello")], ], }, - output: new AIMessage("You are Godzilla\nhello"), + output: new AIMessage({ + id: anyString, + content: "You are Godzilla\nhello", + }), }, }, { @@ -1291,7 +1310,12 @@ test("Chat model that doesn't support streaming, but is invoked, should emit one foo: "bar", }, name: "RunnableLambda", - data: { chunk: new AIMessage("You are Godzilla\nhello") }, + data: { + chunk: new AIMessage({ + id: anyString, + content: "You are Godzilla\nhello", + }), + }, }, { event: "on_chain_stream", @@ -1301,7 +1325,12 @@ test("Chat model that doesn't support streaming, but is invoked, should emit one foo: "bar", }, name: "my_chain", - data: { chunk: new AIMessage("You are Godzilla\nhello") }, + data: { + chunk: new AIMessage({ + id: anyString, + content: "You are Godzilla\nhello", + }), + }, }, { event: "on_chain_end", @@ -1313,7 +1342,10 @@ test("Chat model that doesn't support streaming, but is invoked, should emit one }, data: { input: await template.invoke({ question: "hello" }), - output: new AIMessage("You are Godzilla\nhello"), + output: new AIMessage({ + id: anyString, + content: "You are Godzilla\nhello", + }), }, }, { @@ -1325,7 +1357,10 @@ test("Chat model that doesn't support streaming, but is invoked, should emit one foo: "bar", }, data: { - output: new AIMessage("You are Godzilla\nhello"), + output: new AIMessage({ + id: anyString, + content: "You are Godzilla\nhello", + }), }, }, ]); diff --git a/langchain-core/src/tracers/event_stream.ts b/langchain-core/src/tracers/event_stream.ts index cf9c148c5275..015faa20e5c8 100644 --- a/langchain-core/src/tracers/event_stream.ts +++ b/langchain-core/src/tracers/event_stream.ts @@ -370,7 +370,7 @@ export class EventStreamCallbackHandler extends BaseTracer { if (runInfo.runType === "chat_model") { eventName = "on_chat_model_stream"; if (kwargs?.chunk === undefined) { - chunk = new AIMessageChunk({ content: token }); + chunk = new AIMessageChunk({ content: token, id: `run-${run.id}` }); } else { chunk = kwargs.chunk.message; } diff --git a/langchain-core/src/tracers/log_stream.ts b/langchain-core/src/tracers/log_stream.ts index 004a682dec1b..13b97349b04b 100644 --- a/langchain-core/src/tracers/log_stream.ts +++ b/langchain-core/src/tracers/log_stream.ts @@ -457,7 +457,10 @@ export class LogStreamCallbackHandler extends BaseTracer { if (isChatGenerationChunk(kwargs?.chunk)) { streamedOutputValue = kwargs?.chunk; } else { - streamedOutputValue = new AIMessageChunk(token); + streamedOutputValue = new AIMessageChunk({ + id: `run-${run.id}`, + content: token, + }); } } else { streamedOutputValue = token; From 8f4ba8da702a10456a04889e607f27b31f8456a6 Mon Sep 17 00:00:00 2001 From: Jacob Lee Date: Wed, 28 Aug 2024 14:05:48 -0700 Subject: [PATCH 118/344] core[patch]: Treat OpenAI message format as a message like (#6654) --- .../language_models/tests/chat_models.test.ts | 58 +++++++++++++++++ langchain-core/src/messages/base.ts | 13 ++++ .../src/messages/tests/base_message.test.ts | 62 +++++++++++++++++++ langchain-core/src/messages/utils.ts | 48 +++++++++++++- 4 files changed, 178 insertions(+), 3 deletions(-) diff --git a/langchain-core/src/language_models/tests/chat_models.test.ts b/langchain-core/src/language_models/tests/chat_models.test.ts index 940cc50802b2..70ff187243e8 100644 --- a/langchain-core/src/language_models/tests/chat_models.test.ts +++ b/langchain-core/src/language_models/tests/chat_models.test.ts @@ -27,6 +27,64 @@ test("Test ChatModel accepts object shorthand for messages", async () => { expect(response.content).toEqual("Hello there!"); }); +test("Test ChatModel accepts object with role for messages", async () => { + const model = new FakeChatModel({}); + const response = await model.invoke([ + { + role: "human", + content: "Hello there!!", + example: true, + }, + ]); + expect(response.content).toEqual("Hello there!!"); +}); + +test("Test ChatModel accepts several messages as objects with role", async () => { + const model = new FakeChatModel({}); + const response = await model.invoke([ + { + role: "system", + content: "You are an assistant.", + }, + { + role: "human", + content: [{ type: "text", text: "What is the weather in SF?" }], + example: true, + }, + { + role: "assistant", + content: "", + tool_calls: [ + { + id: "call_123", + function: { + name: "get_weather", + arguments: JSON.stringify({ location: "sf" }), + }, + type: "function", + }, + ], + }, + { + role: "tool", + content: "Pretty nice right now!", + tool_call_id: "call_123", + }, + ]); + expect(response.content).toEqual( + [ + "You are an assistant.", + JSON.stringify( + [{ type: "text", text: "What is the weather in SF?" }], + null, + 2 + ), + "", + "Pretty nice right now!", + ].join("\n") + ); +}); + test("Test ChatModel uses callbacks", async () => { const model = new FakeChatModel({}); let acc = ""; diff --git a/langchain-core/src/messages/base.ts b/langchain-core/src/messages/base.ts index 639fcf226025..7f46ce761221 100644 --- a/langchain-core/src/messages/base.ts +++ b/langchain-core/src/messages/base.ts @@ -450,12 +450,25 @@ export abstract class BaseMessageChunk extends BaseMessage { abstract concat(chunk: BaseMessageChunk): BaseMessageChunk; } +export type MessageFieldWithRole = { + role: StringWithAutocomplete<"user" | "assistant" | MessageType>; + content: MessageContent; + name?: string; +} & Record; + +export function _isMessageFieldWithRole( + x: BaseMessageLike +): x is MessageFieldWithRole { + return typeof (x as MessageFieldWithRole).role === "string"; +} + export type BaseMessageLike = | BaseMessage | ({ type: MessageType | "user" | "assistant" | "placeholder"; } & BaseMessageFields & Record) + | MessageFieldWithRole | [ StringWithAutocomplete< MessageType | "user" | "assistant" | "placeholder" diff --git a/langchain-core/src/messages/tests/base_message.test.ts b/langchain-core/src/messages/tests/base_message.test.ts index cc6926a1e6b6..0e6883c89dc0 100644 --- a/langchain-core/src/messages/tests/base_message.test.ts +++ b/langchain-core/src/messages/tests/base_message.test.ts @@ -6,6 +6,8 @@ import { ToolMessage, ToolMessageChunk, AIMessageChunk, + coerceMessageLikeToMessage, + SystemMessage, } from "../index.js"; import { load } from "../../load/index.js"; @@ -334,3 +336,63 @@ describe("Complex AIMessageChunk concat", () => { ); }); }); + +describe("Message like coercion", () => { + it("Should convert OpenAI format messages", async () => { + const messages = [ + { + id: "foobar", + role: "system", + content: "6", + }, + { + role: "user", + content: [{ type: "image_url", image_url: { url: "7.1" } }], + }, + { + role: "assistant", + content: [{ type: "text", text: "8.1" }], + tool_calls: [ + { + id: "8.5", + function: { + name: "8.4", + arguments: JSON.stringify({ "8.2": "8.3" }), + }, + type: "function", + }, + ], + }, + { + role: "tool", + content: "10.2", + tool_call_id: "10.2", + }, + ].map(coerceMessageLikeToMessage); + expect(messages).toEqual([ + new SystemMessage({ + id: "foobar", + content: "6", + }), + new HumanMessage({ + content: [{ type: "image_url", image_url: { url: "7.1" } }], + }), + new AIMessage({ + content: [{ type: "text", text: "8.1" }], + tool_calls: [ + { + id: "8.5", + name: "8.4", + args: { "8.2": "8.3" }, + type: "tool_call", + }, + ], + }), + new ToolMessage({ + name: undefined, + content: "10.2", + tool_call_id: "10.2", + }), + ]); + }); +}); diff --git a/langchain-core/src/messages/utils.ts b/langchain-core/src/messages/utils.ts index 4a34e03984ee..b9df034ee923 100644 --- a/langchain-core/src/messages/utils.ts +++ b/langchain-core/src/messages/utils.ts @@ -1,3 +1,4 @@ +import { _isToolCall } from "../tools/utils.js"; import { AIMessage, AIMessageChunk, AIMessageChunkFields } from "./ai.js"; import { BaseMessageLike, @@ -6,6 +7,7 @@ import { StoredMessage, StoredMessageV1, BaseMessageFields, + _isMessageFieldWithRole, } from "./base.js"; import { ChatMessage, @@ -19,16 +21,53 @@ import { } from "./function.js"; import { HumanMessage, HumanMessageChunk } from "./human.js"; import { SystemMessage, SystemMessageChunk } from "./system.js"; -import { ToolMessage, ToolMessageFieldsWithToolCallId } from "./tool.js"; +import { + ToolCall, + ToolMessage, + ToolMessageFieldsWithToolCallId, +} from "./tool.js"; + +function _coerceToolCall( + toolCall: ToolCall | Record +): ToolCall { + if (_isToolCall(toolCall)) { + return toolCall; + } else if ( + typeof toolCall.id === "string" && + toolCall.type === "function" && + typeof toolCall.function === "object" && + toolCall.function !== null && + "arguments" in toolCall.function && + typeof toolCall.function.arguments === "string" && + "name" in toolCall.function && + typeof toolCall.function.name === "string" + ) { + // Handle OpenAI tool call format + return { + id: toolCall.id, + args: JSON.parse(toolCall.function.arguments), + name: toolCall.function.name, + type: "tool_call", + }; + } else { + // TODO: Throw an error? + return toolCall as ToolCall; + } +} function _constructMessageFromParams( - params: BaseMessageFields & { type: string } + params: BaseMessageFields & { type: string } & Record ) { const { type, ...rest } = params; if (type === "human" || type === "user") { return new HumanMessage(rest); } else if (type === "ai" || type === "assistant") { - return new AIMessage(rest); + const { tool_calls: rawToolCalls, ...other } = rest; + if (!Array.isArray(rawToolCalls)) { + return new AIMessage(rest); + } + const tool_calls = rawToolCalls.map(_coerceToolCall); + return new AIMessage({ ...other, tool_calls }); } else if (type === "system") { return new SystemMessage(rest); } else if (type === "tool" && "tool_call_id" in rest) { @@ -56,6 +95,9 @@ export function coerceMessageLikeToMessage( if (Array.isArray(messageLike)) { const [type, content] = messageLike; return _constructMessageFromParams({ type, content }); + } else if (_isMessageFieldWithRole(messageLike)) { + const { role: type, ...rest } = messageLike; + return _constructMessageFromParams({ ...rest, type }); } else { return _constructMessageFromParams(messageLike); } From f3d4b149e4ecd40902c58ad54e0abba39c2d5098 Mon Sep 17 00:00:00 2001 From: Jacob Lee Date: Wed, 28 Aug 2024 14:28:36 -0700 Subject: [PATCH 119/344] core[patch]: Release 0.2.31 (#6656) --- 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 cc1de8205949..9bc186af33be 100644 --- a/langchain-core/package.json +++ b/langchain-core/package.json @@ -1,6 +1,6 @@ { "name": "@langchain/core", - "version": "0.2.30", + "version": "0.2.31", "description": "Core LangChain.js abstractions and schemas", "type": "module", "engines": { From f38c569e62ccd2d8517f05c24929e704f1e4ded0 Mon Sep 17 00:00:00 2001 From: Brace Sproul Date: Fri, 30 Aug 2024 14:29:22 -0700 Subject: [PATCH 120/344] api_refs[patch]: Update core redirect (#6668) --- docs/api_refs/vercel.json | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/api_refs/vercel.json b/docs/api_refs/vercel.json index fe54a520726c..219988d6d6e5 100644 --- a/docs/api_refs/vercel.json +++ b/docs/api_refs/vercel.json @@ -114,6 +114,10 @@ "source": "/:path*/langchain_core_:rest", "destination": "/:path*/_langchain_core.:rest" }, + { + "source": "/:path*/langchain_core.:rest", + "destination": "/:path*/_langchain_core.:rest" + }, { "source": "/:path*/langchain_anthropic_experimental(_|\\.):rest", "destination": "/:path*/_langchain_anthropic.experimental.:rest" From aa1f7cf5ef7145281273aad055fd8c9262337753 Mon Sep 17 00:00:00 2001 From: Brace Sproul Date: Mon, 2 Sep 2024 12:53:56 -0700 Subject: [PATCH 121/344] openai[patch]: Skip flaky test (#6677) --- .../src/tests/chat_models_structured_output.int.test.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/libs/langchain-openai/src/tests/chat_models_structured_output.int.test.ts b/libs/langchain-openai/src/tests/chat_models_structured_output.int.test.ts index 650cf0434b5f..c6a6c97dbeda 100644 --- a/libs/langchain-openai/src/tests/chat_models_structured_output.int.test.ts +++ b/libs/langchain-openai/src/tests/chat_models_structured_output.int.test.ts @@ -499,7 +499,8 @@ describe("response_format: json_schema", () => { expect(response).toHaveProperty("unit"); }); - it("can be streamed with WSO", async () => { + // Flaky test + it.skip("can be streamed with WSO", async () => { const model = new ChatOpenAI({ model: "gpt-4o-2024-08-06", }).withStructuredOutput(weatherSchema, { From f3de0bc5ab72a5fd7f623dcb1a71f1a69ff43fd9 Mon Sep 17 00:00:00 2001 From: Jacob Lee Date: Mon, 2 Sep 2024 20:05:28 -0700 Subject: [PATCH 122/344] =?UTF-8?q?=F0=9F=91=A5=20Update=20LangChain=20peo?= =?UTF-8?q?ple=20data=20(#6669)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: github-actions --- docs/core_docs/data/people.yml | 305 ++++++++++++++------------------- 1 file changed, 125 insertions(+), 180 deletions(-) diff --git a/docs/core_docs/data/people.yml b/docs/core_docs/data/people.yml index a4be81c7887a..e73bfda2b9d6 100644 --- a/docs/core_docs/data/people.yml +++ b/docs/core_docs/data/people.yml @@ -1,100 +1,60 @@ maintainers: - login: nfcampos - count: 339 + count: 343 avatarUrl: https://avatars.githubusercontent.com/u/56902?u=fdb30e802c68bc338dd9c0820f713e4fdac75db7&v=4 twitterUsername: nfcampos url: https://github.com/nfcampos -- login: jacoblee93 - count: 1051 - avatarUrl: https://avatars.githubusercontent.com/u/6952323?u=d785f9406c5a78ebd75922567b2693fb643c3bb0&v=4 - twitterUsername: hacubu - url: https://github.com/jacoblee93 +- login: bracesproul + count: 764 + avatarUrl: https://avatars.githubusercontent.com/u/46789226?u=83f467441c4b542b900fe2bb8fe45e26bf918da0&v=4 + twitterUsername: bracesproul + url: https://github.com/bracesproul - login: dqbd - count: 43 + count: 45 avatarUrl: https://avatars.githubusercontent.com/u/1443449?u=fe32372ae8f497065ef0a1c54194d9dff36fb81d&v=4 twitterUsername: __dqbd url: https://github.com/dqbd +- login: jacoblee93 + count: 1226 + avatarUrl: https://avatars.githubusercontent.com/u/6952323?u=d785f9406c5a78ebd75922567b2693fb643c3bb0&v=4 + twitterUsername: hacubu + url: https://github.com/jacoblee93 - login: hwchase17 count: 73 avatarUrl: https://avatars.githubusercontent.com/u/11986836?u=f4c4f21a82b2af6c9f91e1f1d99ea40062f7a101&v=4 twitterUsername: null url: https://github.com/hwchase17 -- login: bracesproul - count: 515 - avatarUrl: https://avatars.githubusercontent.com/u/46789226?u=83f467441c4b542b900fe2bb8fe45e26bf918da0&v=4 - twitterUsername: bracesproul - url: https://github.com/bracesproul top_recent_contributors: -- login: jeasonnow - count: 4.664088958316339 - avatarUrl: https://avatars.githubusercontent.com/u/16950207?u=ab2d0d4f1574398ac842e6bb3c2ba020ab7711eb&v=4 - twitterUsername: null - url: https://github.com/jeasonnow -- login: Anush008 - count: 4.11180681500838 - avatarUrl: https://avatars.githubusercontent.com/u/46051506?u=026f5f140e8b7ba4744bf971f9ebdea9ebab67ca&v=4 - twitterUsername: null - url: https://github.com/Anush008 +- login: dl102306 + count: 2.594246611122073 + avatarUrl: https://avatars.githubusercontent.com/u/2979960?v=4 + twitterUsername: null + url: https://github.com/dl102306 +- login: Anirudh31415926535 + count: 2.378611865453971 + avatarUrl: https://avatars.githubusercontent.com/u/171019460?v=4 + twitterUsername: null + url: https://github.com/Anirudh31415926535 +- login: dependabot + count: 2.118005716372584 + avatarUrl: https://avatars.githubusercontent.com/in/29110?v=4 + twitterUsername: null + url: https://github.com/apps/dependabot +- login: chentschel + count: 1.8419619282928634 + avatarUrl: https://avatars.githubusercontent.com/u/319227?u=db20ce1d424f10d7760665ab693791ebc580131a&v=4 + twitterUsername: chentschel_ + url: https://github.com/chentschel - login: sinedied - count: 3.9993938956370485 + count: 1.5981076527116855 avatarUrl: https://avatars.githubusercontent.com/u/593151?u=08557bbdd96221813b8aec932dd7de895ac040ea&v=4 twitterUsername: sinedied url: https://github.com/sinedied -- login: tomasonjo - count: 2.4717945681400333 - avatarUrl: https://avatars.githubusercontent.com/u/19948365?v=4 - twitterUsername: tb_tomaz - url: https://github.com/tomasonjo -- login: fahreddinozcan - count: 2.44579754228877 - avatarUrl: https://avatars.githubusercontent.com/u/88107904?v=4 - twitterUsername: null - url: https://github.com/fahreddinozcan -- login: easwee - count: 2.334071434196855 - avatarUrl: https://avatars.githubusercontent.com/u/2518825?u=a24026bc5ed35688174b1a36f3c29eda594d38d7&v=4 - twitterUsername: easwee - url: https://github.com/easwee -- login: ovuruska - count: 2.286474945226641 - avatarUrl: https://avatars.githubusercontent.com/u/75265893?u=7f11152d07f1719da22084388c09b5fc64ab6c89&v=4 - twitterUsername: VuruskanerOguz - url: https://github.com/ovuruska -- login: nicoloboschi - count: 2.012290435766335 - avatarUrl: https://avatars.githubusercontent.com/u/23314389?u=2014e20e246530fa89bd902fe703b6f9e6ecf833&v=4 - twitterUsername: nicoloboschi - url: https://github.com/nicoloboschi -- login: mishushakov - count: 1.8894395682001615 - avatarUrl: https://avatars.githubusercontent.com/u/10400064?u=581d97314df325c15ec221f64834003d3bba5cc1&v=4 - twitterUsername: null - url: https://github.com/mishushakov -- login: CahidArda - count: 1.8064422121491948 - avatarUrl: https://avatars.githubusercontent.com/u/57228345?v=4 - twitterUsername: null - url: https://github.com/CahidArda -- login: sarangan12 - count: 1.7550605760866516 - avatarUrl: https://avatars.githubusercontent.com/u/602456?u=d39962c60b0ac5fea4e97cb67433a42c736c3c5b&v=4 - twitterUsername: null - url: https://github.com/sarangan12 -- login: afirstenberg - count: 1.6921412567841918 - avatarUrl: https://avatars.githubusercontent.com/u/3507578?v=4 - twitterUsername: null - url: https://github.com/afirstenberg - login: miloradvojnovic count: 1.5406950154046446 avatarUrl: https://avatars.githubusercontent.com/u/11798350?u=a9b931a1a3319787bef5e2d16e1fdec0888cdad5&v=4 twitterUsername: null url: https://github.com/miloradvojnovic -- login: TeCHiScy - count: 1.4707811568276683 - avatarUrl: https://avatars.githubusercontent.com/u/741195?u=e5937011ef84ff8a4b4b62ac1926a291c04f5d8b&v=4 - twitterUsername: null - url: https://github.com/TeCHiScy - login: anthonychu count: 1.469340114219017 avatarUrl: https://avatars.githubusercontent.com/u/3982077?u=8bbebac42cb84a25c629f83f212b2d099ffa3964&v=4 @@ -105,76 +65,31 @@ top_recent_contributors: avatarUrl: https://avatars.githubusercontent.com/u/4422500?u=d676ede0cec8ee5df6879ebf9d8b72d51ea1eb7f&v=4 twitterUsername: null url: https://github.com/josemussa -- login: cinqisap - count: 1.425069723409155 - avatarUrl: https://avatars.githubusercontent.com/u/158295355?v=4 - twitterUsername: null - url: https://github.com/cinqisap -- login: seuha516 - count: 1.3248369887374036 - avatarUrl: https://avatars.githubusercontent.com/u/79067549?u=de7a2688cb44010afafd055d707f3463585494df&v=4 - twitterUsername: null - url: https://github.com/seuha516 -- login: LordMsz - count: 1.2519987699876998 - avatarUrl: https://avatars.githubusercontent.com/u/33070601?u=ddc6c16156f6397198692c547324e51f94c70ca7&v=4 - twitterUsername: null - url: https://github.com/LordMsz -- login: crazyurus - count: 1.2498577975251404 - avatarUrl: https://avatars.githubusercontent.com/u/2209055?u=b39f7e70f137ff3d1785d261cb15067f0d91ae05&v=4 - twitterUsername: null - url: https://github.com/crazyurus -- login: paul-paliychuk - count: 1.245805528608245 - avatarUrl: https://avatars.githubusercontent.com/u/26054637?u=edd1e4f54e91b549f2edb525d43210f4f04d7367&v=4 - twitterUsername: null - url: https://github.com/paul-paliychuk -- login: Luisotee - count: 1.1353383458646618 - avatarUrl: https://avatars.githubusercontent.com/u/50471205?u=059d6ab166e5a32c496ff50ef6e3fb0ca04a50ad&v=4 - twitterUsername: null - url: https://github.com/Luisotee -- login: Adrastopoulos - count: 1.118279569892473 - avatarUrl: https://avatars.githubusercontent.com/u/76796897?u=0bd50d301b4c7025f29396af44c8e1829eff1db6&v=4 - twitterUsername: null - url: https://github.com/Adrastopoulos -- login: JackFener - count: 1.108656849620705 - avatarUrl: https://avatars.githubusercontent.com/u/20380671?u=b51d10b71850203e6360655fa59cc679c5a498e6&v=4 +- login: ovuruska + count: 1.2073722297602894 + avatarUrl: https://avatars.githubusercontent.com/u/75265893?u=7f11152d07f1719da22084388c09b5fc64ab6c89&v=4 + twitterUsername: VuruskanerOguz + url: https://github.com/ovuruska +- login: tofuliang + count: 1.192831678125796 + avatarUrl: https://avatars.githubusercontent.com/u/1814685?v=4 twitterUsername: null - url: https://github.com/JackFener -- login: ananis25 - count: 1.1039797561536693 - avatarUrl: https://avatars.githubusercontent.com/u/16446513?u=5026326ed39bfee8325c30cdbd24ac20519d21b8&v=4 - twitterUsername: ananis25 - url: https://github.com/ananis25 + url: https://github.com/tofuliang - login: AvaterClasher count: 1.0900805104055902 avatarUrl: https://avatars.githubusercontent.com/u/116944847?u=102a870b3efed7f30f0a57123391a293eb6f5b08&v=4 twitterUsername: Avater004 url: https://github.com/AvaterClasher -- login: nickscamara - count: 1.078170520737719 - avatarUrl: https://avatars.githubusercontent.com/u/20311743?u=29bf2391ae34297a12a88d813731b0bdf289e4a5&v=4 - twitterUsername: nickscamara_ - url: https://github.com/nickscamara +- login: jl4nz + count: 1.071155682903534 + avatarUrl: https://avatars.githubusercontent.com/u/94814971?u=266358610eeb54c3393dc127718dd6a997fdbf52&v=4 + twitterUsername: jlanzarotti + url: https://github.com/jl4nz - login: volodymyr-memsql count: 1.0661641541038527 avatarUrl: https://avatars.githubusercontent.com/u/57520563?v=4 twitterUsername: null url: https://github.com/volodymyr-memsql -- login: madmed88 - count: 1.0614216701173222 - avatarUrl: https://avatars.githubusercontent.com/u/1579388?u=62ca1bfe7c271b5fd1d77abc470aa5e535b1ed83&v=4 - twitterUsername: null - url: https://github.com/madmed88 -- login: WilliamEspegren - count: 1.0091400083090984 - avatarUrl: https://avatars.githubusercontent.com/u/131612909?v=4 - twitterUsername: WilliamEspegren - url: https://github.com/WilliamEspegren top_contributors: - login: afirstenberg count: 22.268597738308145 @@ -197,12 +112,12 @@ top_contributors: twitterUsername: null url: https://github.com/sullivan-sean - login: sinedied - count: 8.92324495771288 + count: 10.521352610424566 avatarUrl: https://avatars.githubusercontent.com/u/593151?u=08557bbdd96221813b8aec932dd7de895ac040ea&v=4 twitterUsername: sinedied url: https://github.com/sinedied - login: tomasonjo - count: 8.186473389575989 + count: 8.253700280332291 avatarUrl: https://avatars.githubusercontent.com/u/19948365?v=4 twitterUsername: tb_tomaz url: https://github.com/tomasonjo @@ -256,21 +171,26 @@ top_contributors: avatarUrl: https://avatars.githubusercontent.com/u/48515433?v=4 twitterUsername: null url: https://github.com/MJDeligan +- login: jeasonnow + count: 5.487271244993293 + avatarUrl: https://avatars.githubusercontent.com/u/16950207?u=ab2d0d4f1574398ac842e6bb3c2ba020ab7711eb&v=4 + twitterUsername: null + url: https://github.com/jeasonnow - login: malandis count: 4.955686027081205 avatarUrl: https://avatars.githubusercontent.com/u/3690240?v=4 twitterUsername: mlonml url: https://github.com/malandis -- login: jeasonnow - count: 4.664088958316339 - avatarUrl: https://avatars.githubusercontent.com/u/16950207?u=ab2d0d4f1574398ac842e6bb3c2ba020ab7711eb&v=4 - twitterUsername: null - url: https://github.com/jeasonnow - login: danielchalef count: 4.393048187010669 avatarUrl: https://avatars.githubusercontent.com/u/131175?u=332fe36f12d9ffe9e4414dc776b381fe801a9c53&v=4 twitterUsername: null url: https://github.com/danielchalef +- login: Swimburger + count: 4.30247126533074 + avatarUrl: https://avatars.githubusercontent.com/u/3382717?u=5a84a173b0e80effc9161502c0848bf06c84bde9&v=4 + twitterUsername: RealSwimburger + url: https://github.com/Swimburger - login: Anush008 count: 4.11180681500838 avatarUrl: https://avatars.githubusercontent.com/u/46051506?u=026f5f140e8b7ba4744bf971f9ebdea9ebab67ca&v=4 @@ -286,6 +206,11 @@ top_contributors: avatarUrl: https://avatars.githubusercontent.com/u/20127759?v=4 twitterUsername: zukerpie url: https://github.com/kwkr +- login: sarangan12 + count: 3.814606725038421 + avatarUrl: https://avatars.githubusercontent.com/u/602456?u=d39962c60b0ac5fea4e97cb67433a42c736c3c5b&v=4 + twitterUsername: null + url: https://github.com/sarangan12 - login: fahreddinozcan count: 3.6832424135364583 avatarUrl: https://avatars.githubusercontent.com/u/88107904?v=4 @@ -296,16 +221,11 @@ top_contributors: avatarUrl: https://avatars.githubusercontent.com/u/12423122?u=681de0c470e9b349963ee935ddfd6b2e097e7181&v=4 twitterUsername: null url: https://github.com/ewfian -- login: Swimburger - count: 3.5277971498745018 - avatarUrl: https://avatars.githubusercontent.com/u/3382717?u=5a84a173b0e80effc9161502c0848bf06c84bde9&v=4 - twitterUsername: RealSwimburger - url: https://github.com/Swimburger -- login: sarangan12 - count: 3.3730482834799793 - avatarUrl: https://avatars.githubusercontent.com/u/602456?u=d39962c60b0ac5fea4e97cb67433a42c736c3c5b&v=4 - twitterUsername: null - url: https://github.com/sarangan12 +- login: jl4nz + count: 3.322486583856189 + avatarUrl: https://avatars.githubusercontent.com/u/94814971?u=266358610eeb54c3393dc127718dd6a997fdbf52&v=4 + twitterUsername: jlanzarotti + url: https://github.com/jl4nz - login: volodymyr-memsql count: 3.2697631770291293 avatarUrl: https://avatars.githubusercontent.com/u/57520563?v=4 @@ -318,12 +238,12 @@ top_contributors: url: https://github.com/jasondotparse - login: mishushakov count: 3.025937666523869 - avatarUrl: https://avatars.githubusercontent.com/u/10400064?u=581d97314df325c15ec221f64834003d3bba5cc1&v=4 - twitterUsername: null + avatarUrl: https://avatars.githubusercontent.com/u/10400064?u=52b50611d587317f397a96f898753099d65931f1&v=4 + twitterUsername: mishushakov url: https://github.com/mishushakov - login: kristianfreeman count: 2.910863456801872 - avatarUrl: https://avatars.githubusercontent.com/u/922353?u=ad00df1efd8f04a469de6087ee3cd7d7012533f7&v=4 + avatarUrl: https://avatars.githubusercontent.com/u/922353?u=212a67ff65d67d39e41c3cb58cd7a7b8b2f89f3e&v=4 twitterUsername: null url: https://github.com/kristianfreeman - login: neebdev @@ -371,6 +291,11 @@ top_contributors: avatarUrl: https://avatars.githubusercontent.com/u/255413?v=4 twitterUsername: null url: https://github.com/sunner +- login: dl102306 + count: 2.594246611122073 + avatarUrl: https://avatars.githubusercontent.com/u/2979960?v=4 + twitterUsername: null + url: https://github.com/dl102306 - login: rahilvora count: 2.5339652606929115 avatarUrl: https://avatars.githubusercontent.com/u/5127548?u=0cd74312c28da39646785409fb0a37a9b3d3420a&v=4 @@ -401,6 +326,11 @@ top_contributors: avatarUrl: https://avatars.githubusercontent.com/u/3254549?u=794d178a761379e162a1092c556e98a9ec5c2410&v=4 twitterUsername: null url: https://github.com/alexleventer +- login: Anirudh31415926535 + count: 2.378611865453971 + avatarUrl: https://avatars.githubusercontent.com/u/171019460?v=4 + twitterUsername: null + url: https://github.com/Anirudh31415926535 - login: 3eif count: 2.323209691414097 avatarUrl: https://avatars.githubusercontent.com/u/29833473?u=37b8f7a25883ee98bc6b6bd6029c6d5479724e2f&v=4 @@ -426,11 +356,6 @@ top_contributors: avatarUrl: https://avatars.githubusercontent.com/u/501632?u=a714d65c000d8f489f9fc2363f9a372b0dba05e3&v=4 twitterUsername: null url: https://github.com/jo32 -- login: jl4nz - count: 2.251330900952655 - avatarUrl: https://avatars.githubusercontent.com/u/94814971?u=266358610eeb54c3393dc127718dd6a997fdbf52&v=4 - twitterUsername: jlanzarotti - url: https://github.com/jl4nz - login: RohitMidha23 count: 2.232630526458589 avatarUrl: https://avatars.githubusercontent.com/u/38888530?u=5c4b99eff970e551e5b756f270aa5234bc666316&v=4 @@ -461,6 +386,11 @@ top_contributors: avatarUrl: https://avatars.githubusercontent.com/u/17096641?u=9b935c68c077d53642c1b4aff62f04d08e2ffac7&v=4 twitterUsername: null url: https://github.com/davidfant +- login: dependabot + count: 2.118005716372584 + avatarUrl: https://avatars.githubusercontent.com/in/29110?v=4 + twitterUsername: null + url: https://github.com/apps/dependabot - login: MthwRobinson count: 2.106176213349417 avatarUrl: https://avatars.githubusercontent.com/u/1635179?u=0631cb84ca580089198114f94d9c27efe730220e&v=4 @@ -501,6 +431,11 @@ top_contributors: avatarUrl: https://avatars.githubusercontent.com/u/41517614?u=ba37c9a21db3068953ae50d90c1cd07c3dec3abd&v=4 twitterUsername: valdozzz1 url: https://github.com/valdo99 +- login: chentschel + count: 1.8419619282928634 + avatarUrl: https://avatars.githubusercontent.com/u/319227?u=db20ce1d424f10d7760665ab693791ebc580131a&v=4 + twitterUsername: chentschel_ + url: https://github.com/chentschel - login: gmpetrov count: 1.8103159579166737 avatarUrl: https://avatars.githubusercontent.com/u/4693180?u=8cf781d9099d6e2f2d2caf7612a5c2811ba13ef8&v=4 @@ -551,6 +486,11 @@ top_contributors: avatarUrl: https://avatars.githubusercontent.com/u/9033201?u=e5f657c3a1657c089d7cb88121e544ae7212e6f1&v=4 twitterUsername: MerbinJAnselm url: https://github.com/anselm94 +- login: aixgeek + count: 1.5850862534183767 + avatarUrl: https://avatars.githubusercontent.com/u/9697715?u=d139c5568375c2472ac6142325e6856cd766d88d&v=4 + twitterUsername: geekxai + url: https://github.com/aixgeek - login: gramliu count: 1.5791775791775793 avatarUrl: https://avatars.githubusercontent.com/u/24856195?u=9f55337506cdcac3146772c56b4634e6b46a5e46&v=4 @@ -591,6 +531,11 @@ top_contributors: avatarUrl: https://avatars.githubusercontent.com/u/249891?u=36703ce68261be59109622877012be08fbc090da&v=4 twitterUsername: conroywhitney url: https://github.com/conroywhitney +- login: seuha516 + count: 1.511875544931005 + avatarUrl: https://avatars.githubusercontent.com/u/79067549?u=de7a2688cb44010afafd055d707f3463585494df&v=4 + twitterUsername: null + url: https://github.com/seuha516 - login: Czechh count: 1.4922336407937202 avatarUrl: https://avatars.githubusercontent.com/u/4779936?u=ab072503433effc18c071b31adda307988877d5e&v=4 @@ -666,6 +611,11 @@ top_contributors: avatarUrl: https://avatars.githubusercontent.com/u/7654246?u=b8599019655adaada3cdc3c3006798df42c44494&v=4 twitterUsername: andrewnguonly url: https://github.com/andrewnguonly +- login: clemenspeters + count: 1.4063956990937188 + avatarUrl: https://avatars.githubusercontent.com/u/13015002?u=059c556d90a2e5639dee42123077d51223c190f0&v=4 + twitterUsername: PetersClemens + url: https://github.com/clemenspeters - login: ShaunBaker count: 1.4047837363443452 avatarUrl: https://avatars.githubusercontent.com/u/1176557?u=c2e8ecfb45b736fc4d3bbfe182e26936bd519fd3&v=4 @@ -716,16 +666,6 @@ top_contributors: avatarUrl: https://avatars.githubusercontent.com/u/70933?u=206fafc72fd14b4291cb29269c5e1cc8081d043b&v=4 twitterUsername: willemmulder url: https://github.com/willemmulder -- login: aixgeek - count: 1.3311179994501228 - avatarUrl: https://avatars.githubusercontent.com/u/9697715?u=d139c5568375c2472ac6142325e6856cd766d88d&v=4 - twitterUsername: geekxai - url: https://github.com/aixgeek -- login: seuha516 - count: 1.3248369887374036 - avatarUrl: https://avatars.githubusercontent.com/u/79067549?u=de7a2688cb44010afafd055d707f3463585494df&v=4 - twitterUsername: null - url: https://github.com/seuha516 - login: mhart count: 1.314023891343479 avatarUrl: https://avatars.githubusercontent.com/u/367936?v=4 @@ -753,8 +693,8 @@ top_contributors: url: https://github.com/Neverland3124 - login: jasonnathan count: 1.2936835460554628 - avatarUrl: https://avatars.githubusercontent.com/u/780157?u=d5efec16b5e3a9913dc44967059a70d9a610755d&v=4 - twitterUsername: null + avatarUrl: https://avatars.githubusercontent.com/u/780157?u=006e0deda897eb1a4abcc459adcd7242dcbe8fee&v=4 + twitterUsername: jason_nathan url: https://github.com/jasonnathan - login: Maanethdesilva count: 1.2846497764530551 @@ -811,6 +751,11 @@ top_contributors: avatarUrl: https://avatars.githubusercontent.com/u/33070601?u=ddc6c16156f6397198692c547324e51f94c70ca7&v=4 twitterUsername: null url: https://github.com/LordMsz +- login: tanyaasharma + count: 1.2499106738092052 + avatarUrl: https://avatars.githubusercontent.com/u/140478067?v=4 + twitterUsername: null + url: https://github.com/tanyaasharma - login: crazyurus count: 1.2498577975251404 avatarUrl: https://avatars.githubusercontent.com/u/2209055?u=b39f7e70f137ff3d1785d261cb15067f0d91ae05&v=4 @@ -866,11 +811,11 @@ top_contributors: avatarUrl: https://avatars.githubusercontent.com/u/7506913?u=42c32ca59ae2e44532cd45027e5b62d2712cf2a2&v=4 twitterUsername: null url: https://github.com/zhengxs2018 -- login: clemenspeters - count: 1.189464482162502 - avatarUrl: https://avatars.githubusercontent.com/u/13015002?u=059c556d90a2e5639dee42123077d51223c190f0&v=4 - twitterUsername: PetersClemens - url: https://github.com/clemenspeters +- login: tofuliang + count: 1.192831678125796 + avatarUrl: https://avatars.githubusercontent.com/u/1814685?v=4 + twitterUsername: null + url: https://github.com/tofuliang - login: cmtoomey count: 1.1890213611525087 avatarUrl: https://avatars.githubusercontent.com/u/12201602?u=ea5cbb8d158980f6050dd41ae41b7f72e0a47337&v=4 @@ -1108,7 +1053,7 @@ top_contributors: url: https://github.com/erhant top_reviewers: - login: afirstenberg - count: 21 + count: 23 avatarUrl: https://avatars.githubusercontent.com/u/3507578?v=4 twitterUsername: null url: https://github.com/afirstenberg @@ -1132,13 +1077,13 @@ top_reviewers: avatarUrl: https://avatars.githubusercontent.com/u/455796?v=4 twitterUsername: null url: https://github.com/jacobrosenthal +- login: sinedied + count: 6 + avatarUrl: https://avatars.githubusercontent.com/u/593151?u=08557bbdd96221813b8aec932dd7de895ac040ea&v=4 + twitterUsername: sinedied + url: https://github.com/sinedied - login: mieslep count: 5 avatarUrl: https://avatars.githubusercontent.com/u/5420540?u=8f038c002fbce42427999eb715dc9f868cef1c84&v=4 twitterUsername: null url: https://github.com/mieslep -- login: sinedied - count: 5 - avatarUrl: https://avatars.githubusercontent.com/u/593151?u=08557bbdd96221813b8aec932dd7de895ac040ea&v=4 - twitterUsername: sinedied - url: https://github.com/sinedied From 2de468fac67bef08ba8ce5dde13d3daa3ce798c8 Mon Sep 17 00:00:00 2001 From: aya <15815271+ayatnkw@users.noreply.github.com> Date: Tue, 3 Sep 2024 17:57:47 +0200 Subject: [PATCH 123/344] docs[patch]: Fix typos in Agents tutorial (#6675) --- docs/core_docs/docs/tutorials/agents.mdx | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/core_docs/docs/tutorials/agents.mdx b/docs/core_docs/docs/tutorials/agents.mdx index 201339008d9d..d3c0ad4ef4f0 100644 --- a/docs/core_docs/docs/tutorials/agents.mdx +++ b/docs/core_docs/docs/tutorials/agents.mdx @@ -26,7 +26,7 @@ In this tutorial we will build an agent that can interact with multiple differen By definition, agents take a self-determined, input-dependent sequence of steps before returning a user-facing output. This makes debugging these systems particularly tricky, and observability particularly important. [LangSmith](https://smith.langchain.com) is especially useful for such cases. -When building with LangChain, all steps will automatically be traced in LangSmith. To set up LangSmith we just need set the following environment variables: +When building with LangChain, all steps will automatically be traced in LangSmith. To set up LangSmith we just need to set the following environment variables: ```bash export LANGCHAIN_TRACING_V2="true" @@ -42,7 +42,7 @@ We first need to create the tools we want to use. We will use two tools: [Tavily ### [Tavily](https://app.tavily.com) -We have a built-in tool in LangChain to easily use Tavily search engine as tool. +We have a built-in tool in LangChain to easily use Tavily search engine as a tool. Note that this requires a Tavily API key set as an environment variable named `TAVILY_API_KEY` - they have a free tier, but if you don’t have one or don’t want to create one, you can always ignore this step. ```typescript @@ -100,7 +100,7 @@ console.log(retrieverResult[0]); */ ``` -Now that we have populated our index that we will do doing retrieval over, we can easily turn it into a tool (the format needed for an agent to properly use it): +Now that we have populated our index that we will be doing retrieval over, we can easily turn it into a tool (the format needed for an agent to properly use it): ```typescript import { createRetrieverTool } from "langchain/tools/retriever"; @@ -150,7 +150,7 @@ const prompt = await pull( ``` Now, we can initalize the agent with the LLM, the prompt, and the tools. The agent is responsible for taking in input and deciding what actions to take. -Crucially, the Agent does not execute those actions - that is done by the AgentExecutor (next step). For more information about how to thing about these components, see our [conceptual guide](/docs/concepts#agents). +Crucially, the Agent does not execute those actions - that is done by the AgentExecutor (next step). For more information about how to think about these components, see our [conceptual guide](/docs/concepts#agents). ```typescript import { createOpenAIFunctionsAgent } from "langchain/agents"; @@ -163,7 +163,7 @@ const agent = await createOpenAIFunctionsAgent({ ``` Finally, we combine the agent (the brains) with the tools inside the AgentExecutor (which will repeatedly call the agent and execute tools). -For more information about how to thing about these components, see our [conceptual guide](/docs/concepts#agents). +For more information about how to think about these components, see our [conceptual guide](/docs/concepts#agents). ```typescript import { AgentExecutor } from "langchain/agents"; From 7922aa9aa890431eaa5294fc5171e7d0d8c1d92d Mon Sep 17 00:00:00 2001 From: Ezra Chu Date: Wed, 4 Sep 2024 14:12:55 -0400 Subject: [PATCH 124/344] fix(openai): Check `configuration.apiKey` in `ChatOpenAI` constructor (#6664) --- libs/langchain-openai/src/chat_models.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/libs/langchain-openai/src/chat_models.ts b/libs/langchain-openai/src/chat_models.ts index cedb15777527..f2219bd5a5d1 100644 --- a/libs/langchain-openai/src/chat_models.ts +++ b/libs/langchain-openai/src/chat_models.ts @@ -956,6 +956,7 @@ export class ChatOpenAI< this.openAIApiKey = fields?.apiKey ?? fields?.openAIApiKey ?? + fields?.configuration?.apiKey ?? getEnvironmentVariable("OPENAI_API_KEY"); this.apiKey = this.openAIApiKey; From 2cd7519c3095e36cec73d415cf6a2b80b7888923 Mon Sep 17 00:00:00 2001 From: Jacob Lee Date: Wed, 4 Sep 2024 11:20:46 -0700 Subject: [PATCH 125/344] langchain[patch]: Allow custom handling of tool runtime errors in legacy agent executor (#6687) --- langchain/package.json | 6 +-- langchain/src/agents/executor.ts | 9 +++- .../create_openai_tools_agent.int.test.ts | 41 +++++++++++++++++++ 3 files changed, 52 insertions(+), 4 deletions(-) diff --git a/langchain/package.json b/langchain/package.json index 3465af3054fe..2ec614901958 100644 --- a/langchain/package.json +++ b/langchain/package.json @@ -584,9 +584,9 @@ "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": "yarn run build:deps && NODE_OPTIONS=--experimental-vm-modules jest --watch --testPathIgnorePatterns=\\.int\\.test.ts", - "test:integration": "yarn run build:deps && NODE_OPTIONS=--experimental-vm-modules jest --testPathPattern=\\.int\\.test.ts --testTimeout 100000 --maxWorkers=50%", - "test:single": "yarn run build:deps && NODE_OPTIONS=--experimental-vm-modules yarn run jest --config jest.config.cjs --testTimeout 100000", + "test:watch": "NODE_OPTIONS=--experimental-vm-modules jest --watch --testPathIgnorePatterns=\\.int\\.test.ts", + "test:integration": "NODE_OPTIONS=--experimental-vm-modules jest --testPathPattern=\\.int\\.test.ts --testTimeout 100000 --maxWorkers=50%", + "test:single": "NODE_OPTIONS=--experimental-vm-modules yarn run jest --config jest.config.cjs --testTimeout 100000", "format": "prettier --config .prettierrc --write \"src\"", "format:check": "prettier --config .prettierrc --check \"src\"" }, diff --git a/langchain/src/agents/executor.ts b/langchain/src/agents/executor.ts index 70edc5a6573e..5e55da1e311e 100644 --- a/langchain/src/agents/executor.ts +++ b/langchain/src/agents/executor.ts @@ -311,6 +311,7 @@ export interface AgentExecutorInput extends ChainInputs { | boolean | string | ((e: OutputParserException | ToolInputParsingException) => string); + handleToolRuntimeErrors?: (e: Error) => string; } // TODO: Type properly with { intermediateSteps?: AgentStep[] }; @@ -386,6 +387,8 @@ export class AgentExecutor extends BaseChain { | ((e: OutputParserException | ToolInputParsingException) => string) = false; + handleToolRuntimeErrors?: (e: Error) => string; + get inputKeys() { return this.agent.inputKeys; } @@ -427,6 +430,7 @@ export class AgentExecutor extends BaseChain { this.tools = input.tools; this.handleParsingErrors = input.handleParsingErrors ?? this.handleParsingErrors; + this.handleToolRuntimeErrors = input.handleToolRuntimeErrors; this.returnOnlyOutputs = returnOnlyOutputs; if (this.agent._agentActionType() === "multi") { for (const tool of this.tools) { @@ -565,7 +569,8 @@ export class AgentExecutor extends BaseChain { "Received unsupported non-string response from tool call." ); } - } catch (e) { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + } catch (e: any) { // eslint-disable-next-line no-instanceof/no-instanceof if (e instanceof ToolInputParsingException) { if (this.handleParsingErrors === true) { @@ -583,6 +588,8 @@ export class AgentExecutor extends BaseChain { runManager?.getChild() ); return { action, observation: observation ?? "" }; + } else if (this.handleToolRuntimeErrors !== undefined) { + observation = this.handleToolRuntimeErrors(e); } } diff --git a/langchain/src/agents/tests/create_openai_tools_agent.int.test.ts b/langchain/src/agents/tests/create_openai_tools_agent.int.test.ts index 472e60a28d17..8d4c1b6305fd 100644 --- a/langchain/src/agents/tests/create_openai_tools_agent.int.test.ts +++ b/langchain/src/agents/tests/create_openai_tools_agent.int.test.ts @@ -4,6 +4,8 @@ import type { ChatPromptTemplate } from "@langchain/core/prompts"; import { RunnableLambda } from "@langchain/core/runnables"; import { LangChainTracer } from "@langchain/core/tracers/tracer_langchain"; import { AsyncLocalStorageProviderSingleton } from "@langchain/core/singletons"; +import { tool } from "@langchain/core/tools"; +import { z } from "zod"; import { AsyncLocalStorage } from "async_hooks"; import { TavilySearchResults } from "../../util/testing/tools/tavily_search.js"; import { pull } from "../../hub.js"; @@ -40,6 +42,45 @@ test("createOpenAIToolsAgent works", async () => { expect(result.output.length).toBeGreaterThan(10); }); +test("createOpenAIToolsAgent handles errors", async () => { + const errorTools = [ + tool( + async () => { + const error = new Error("Error getting search results"); + throw error; + }, + { + name: "search-results", + schema: z.object({ + query: z.string(), + }), + description: "Searches the web", + } + ), + ]; + const prompt = await pull("hwchase17/openai-tools-agent"); + const llm = new ChatOpenAI({ + modelName: "gpt-3.5-turbo-1106", + temperature: 0, + }); + const agent = await createOpenAIToolsAgent({ + llm, + tools: errorTools, + prompt, + }); + const agentExecutor = new AgentExecutor({ + agent, + tools: errorTools, + handleToolRuntimeErrors: (e) => { + throw e; + }, + }); + const input = "what is LangChain?"; + await expect(agentExecutor.invoke({ input })).rejects.toThrowError( + "Error getting search results" + ); +}); + test.skip("createOpenAIToolsAgent tracing works when it is nested in a lambda", async () => { AsyncLocalStorageProviderSingleton.initializeGlobalInstance( new AsyncLocalStorage() From cc468259be629bc0656b632b23f075a9b6148986 Mon Sep 17 00:00:00 2001 From: Jacob Lee Date: Wed, 4 Sep 2024 12:23:24 -0700 Subject: [PATCH 126/344] chore(openai): Release 0.2.9 (#6689) --- libs/langchain-openai/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libs/langchain-openai/package.json b/libs/langchain-openai/package.json index 1a7bc2f35490..f4d9754b0877 100644 --- a/libs/langchain-openai/package.json +++ b/libs/langchain-openai/package.json @@ -1,6 +1,6 @@ { "name": "@langchain/openai", - "version": "0.2.8", + "version": "0.2.9", "description": "OpenAI integrations for LangChain.js", "type": "module", "engines": { From bc1dadc9bfdf9f77d50ff57f7017c099232cb80c Mon Sep 17 00:00:00 2001 From: Jacob Lee Date: Wed, 4 Sep 2024 12:29:04 -0700 Subject: [PATCH 127/344] fix(community): ElasticVectorSearch: exclude metadata filters not working due changing to term instead of terms (#6688) Co-authored-by: Laurens Tsestigh <90600075+LaurensTsestigh@users.noreply.github.com> --- .../langchain-community/src/vectorstores/elasticsearch.ts | 7 ++++--- .../src/vectorstores/tests/elasticsearch.int.test.ts | 8 ++++++++ 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/libs/langchain-community/src/vectorstores/elasticsearch.ts b/libs/langchain-community/src/vectorstores/elasticsearch.ts index 14a4ee12ac03..cccc439bb7a9 100644 --- a/libs/langchain-community/src/vectorstores/elasticsearch.ts +++ b/libs/langchain-community/src/vectorstores/elasticsearch.ts @@ -343,10 +343,11 @@ export class ElasticVectorSearch extends VectorStore { }, }); } else if (condition.operator === "exclude") { + const toExclude = { [metadataField]: condition.value }; must_not.push({ - term: { - [metadataField]: condition.value, - }, + ...(Array.isArray(condition.value) + ? { terms: toExclude } + : { term: toExclude }), }); } else if (condition.operator === "or") { should.push({ diff --git a/libs/langchain-community/src/vectorstores/tests/elasticsearch.int.test.ts b/libs/langchain-community/src/vectorstores/tests/elasticsearch.int.test.ts index 08ec39ac0f57..41045196892a 100644 --- a/libs/langchain-community/src/vectorstores/tests/elasticsearch.int.test.ts +++ b/libs/langchain-community/src/vectorstores/tests/elasticsearch.int.test.ts @@ -114,6 +114,14 @@ describe("ElasticVectorSearch", () => { }, ]); expect(results2).toHaveLength(1); + const results3 = await store.similaritySearch("*", 11, [ + { + field: "a", + value: [createdAt], + operator: "exclude", + }, + ]); + expect(results3).toHaveLength(1); }); test.skip("ElasticVectorSearch integration with text splitting metadata", async () => { From 26612d90f4468bfd5d957eeb862ace0ac3ed39f6 Mon Sep 17 00:00:00 2001 From: Daniel Wabyick Date: Wed, 4 Sep 2024 12:51:33 -0700 Subject: [PATCH 128/344] Fix tiny typo in llm_chain.ipynb (#6679) --- docs/core_docs/docs/tutorials/llm_chain.ipynb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/core_docs/docs/tutorials/llm_chain.ipynb b/docs/core_docs/docs/tutorials/llm_chain.ipynb index 592fef3cb35b..ce8f7f400b02 100644 --- a/docs/core_docs/docs/tutorials/llm_chain.ipynb +++ b/docs/core_docs/docs/tutorials/llm_chain.ipynb @@ -269,7 +269,7 @@ "source": [ "## Prompt Templates\n", "\n", - "Right now we are passing a list of messages directly into the language model. Where does this list of messages come from? Usually it constructed from a combination of user input and application logic. This application logic usually takes the raw user input and transforms it into a list of messages ready to pass to the language model. Common transformations include adding a system message or formatting a template with the user input.\n", + "Right now we are passing a list of messages directly into the language model. Where does this list of messages come from? Usually it is constructed from a combination of user input and application logic. This application logic usually takes the raw user input and transforms it into a list of messages ready to pass to the language model. Common transformations include adding a system message or formatting a template with the user input.\n", "\n", "PromptTemplates are a concept in LangChain designed to assist with this transformation. They take in raw user input and return data (a prompt) that is ready to pass into a language model. \n", "\n", From eeb7ae75e4a9269b09b14877f806ce758e4caa42 Mon Sep 17 00:00:00 2001 From: rxliuli Date: Thu, 5 Sep 2024 04:10:03 +0800 Subject: [PATCH 129/344] feat(anthropic): langchain-anthropic support custom create anthropic client (#6615) Co-authored-by: jacoblee93 --- .../docs/integrations/chat/anthropic.ipynb | 30 ++++++++++++++ docs/core_docs/package.json | 1 + libs/langchain-anthropic/package.json | 1 + libs/langchain-anthropic/src/chat_models.ts | 23 ++++++++++- .../src/tests/chat_models.int.test.ts | 14 +++++++ yarn.lock | 41 +++++++++++++++++++ 6 files changed, 108 insertions(+), 2 deletions(-) diff --git a/docs/core_docs/docs/integrations/chat/anthropic.ipynb b/docs/core_docs/docs/integrations/chat/anthropic.ipynb index 7cf4bbb7c6d0..4786d1f68a9b 100644 --- a/docs/core_docs/docs/integrations/chat/anthropic.ipynb +++ b/docs/core_docs/docs/integrations/chat/anthropic.ipynb @@ -931,6 +931,36 @@ "For more on how prompt caching works, see [Anthropic's docs](https://docs.anthropic.com/en/docs/build-with-claude/prompt-caching#how-prompt-caching-works)." ] }, + { + "cell_type": "markdown", + "id": "f8dece4e", + "metadata": {}, + "source": [ + "## Custom clients\n", + "\n", + "Anthropic models [may be hosted on cloud services such as Google Vertex](https://docs.anthropic.com/en/api/claude-on-vertex-ai) that rely on a different underlying client with the same interface as the primary Anthropic client. You can access these services by providing a `createClient` method that returns an initialized instance of an Anthropic client. Here's an example:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "00ec6d41", + "metadata": {}, + "outputs": [], + "source": [ + "import { AnthropicVertex } from \"@anthropic-ai/vertex-sdk\";\n", + "\n", + "const customClient = new AnthropicVertex();\n", + "\n", + "const modelWithCustomClient = new ChatAnthropic({\n", + " modelName: \"claude-3-sonnet-20240229\",\n", + " maxRetries: 0,\n", + " createClient: () => customClient,\n", + "});\n", + "\n", + "await modelWithCustomClient.invoke([{ role: \"user\", content: \"Hello!\" }]);" + ] + }, { "cell_type": "markdown", "id": "3a5bb5ca-c3ae-4a58-be67-2cd18574b9a3", diff --git a/docs/core_docs/package.json b/docs/core_docs/package.json index 9a74b1014673..ee79afc2e105 100644 --- a/docs/core_docs/package.json +++ b/docs/core_docs/package.json @@ -30,6 +30,7 @@ "validate": "yarn notebook_validate" }, "dependencies": { + "@anthropic-ai/vertex-sdk": "^0.4.1", "@docusaurus/core": "2.4.3", "@docusaurus/preset-classic": "2.4.3", "@docusaurus/remark-plugin-npm2yarn": "2.4.3", diff --git a/libs/langchain-anthropic/package.json b/libs/langchain-anthropic/package.json index fe8027f36ffb..6e037683cd15 100644 --- a/libs/langchain-anthropic/package.json +++ b/libs/langchain-anthropic/package.json @@ -42,6 +42,7 @@ "zod-to-json-schema": "^3.22.4" }, "devDependencies": { + "@anthropic-ai/vertex-sdk": "^0.4.1", "@jest/globals": "^29.5.0", "@langchain/scripts": ">=0.1.0 <0.2.0", "@langchain/standard-tests": "0.0.0", diff --git a/libs/langchain-anthropic/src/chat_models.ts b/libs/langchain-anthropic/src/chat_models.ts index 11d683cba8cc..1d561194faa7 100644 --- a/libs/langchain-anthropic/src/chat_models.ts +++ b/libs/langchain-anthropic/src/chat_models.ts @@ -145,6 +145,14 @@ export interface AnthropicInput { * @default true */ streamUsage?: boolean; + + /** + * Optional method that returns an initialized underlying Anthropic client. + * Useful for accessing Anthropic models hosted on other cloud services + * such as Google Vertex. + */ + // eslint-disable-next-line @typescript-eslint/no-explicit-any + createClient?: (options: ClientOptions) => any; } /** @@ -611,6 +619,13 @@ export class ChatAnthropicMessages< streamUsage = true; + /** + * Optional method that returns an initialized underlying Anthropic client. + * Useful for accessing Anthropic models hosted on other cloud services + * such as Google Vertex. + */ + createClient: (options: ClientOptions) => Anthropic; + constructor(fields?: AnthropicInput & BaseChatModelParams) { super(fields ?? {}); @@ -644,6 +659,10 @@ export class ChatAnthropicMessages< this.streaming = fields?.streaming ?? false; this.streamUsage = fields?.streamUsage ?? this.streamUsage; + + this.createClient = + fields?.createClient ?? + ((options: ClientOptions) => new Anthropic(options)); } getLsParams(options: this["ParsedCallOptions"]): LangSmithParams { @@ -908,7 +927,7 @@ export class ChatAnthropicMessages< ): Promise> { if (!this.streamingClient) { const options_ = this.apiUrl ? { baseURL: this.apiUrl } : undefined; - this.streamingClient = new Anthropic({ + this.streamingClient = this.createClient({ ...this.clientOptions, ...options_, apiKey: this.apiKey, @@ -938,7 +957,7 @@ export class ChatAnthropicMessages< if (!this.apiKey) { throw new Error("Missing Anthropic API key."); } - this.batchClient = new Anthropic({ + this.batchClient = this.createClient({ ...this.clientOptions, ...options, apiKey: this.apiKey, diff --git a/libs/langchain-anthropic/src/tests/chat_models.int.test.ts b/libs/langchain-anthropic/src/tests/chat_models.int.test.ts index 29d12a22dcb5..d6864e92aa3d 100644 --- a/libs/langchain-anthropic/src/tests/chat_models.int.test.ts +++ b/libs/langchain-anthropic/src/tests/chat_models.int.test.ts @@ -16,6 +16,7 @@ import { } from "@langchain/core/prompts"; import { CallbackManager } from "@langchain/core/callbacks/manager"; import { concat } from "@langchain/core/utils/stream"; +import { AnthropicVertex } from "@anthropic-ai/vertex-sdk"; import { ChatAnthropic } from "../chat_models.js"; test("Test ChatAnthropic", async () => { @@ -735,3 +736,16 @@ test.skip("tool caching", async () => { 0 ); }); + +test.skip("Test ChatAnthropic with custom client", async () => { + const client = new AnthropicVertex(); + const chat = new ChatAnthropic({ + modelName: "claude-3-sonnet-20240229", + maxRetries: 0, + createClient: () => client, + }); + const message = new HumanMessage("Hello!"); + const res = await chat.invoke([message]); + // console.log({ res }); + expect(res.response_metadata.usage).toBeDefined(); +}); diff --git a/yarn.lock b/yarn.lock index 48c3049c4726..1f7b98b5f78c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -211,6 +211,21 @@ __metadata: languageName: node linkType: hard +"@anthropic-ai/sdk@npm:>=0.14 <1": + version: 0.27.0 + resolution: "@anthropic-ai/sdk@npm:0.27.0" + dependencies: + "@types/node": ^18.11.18 + "@types/node-fetch": ^2.6.4 + abort-controller: ^3.0.0 + agentkeepalive: ^4.2.1 + form-data-encoder: 1.7.2 + formdata-node: ^4.3.2 + node-fetch: ^2.6.7 + checksum: 5cb980769a38a660fa607d9985f4cdae7aabc49053b94fe892f6c3b30f2714c32c6e86c6cf94c8bce9ceea6eed2cd5bac2e1d9d58c9aad0da43681f0a060263d + languageName: node + linkType: hard + "@anthropic-ai/sdk@npm:^0.25.2": version: 0.25.2 resolution: "@anthropic-ai/sdk@npm:0.25.2" @@ -226,6 +241,16 @@ __metadata: languageName: node linkType: hard +"@anthropic-ai/vertex-sdk@npm:^0.4.1": + version: 0.4.1 + resolution: "@anthropic-ai/vertex-sdk@npm:0.4.1" + dependencies: + "@anthropic-ai/sdk": ">=0.14 <1" + google-auth-library: ^9.4.2 + checksum: 3983c6f1b0e0aa2ce6896a0269177d1374343ba86169a30ad6cc0d9beef40ddcf410159baaa3f0942afbf62930951d067a870aa5c5e0624fe2c47981dc8d8c55 + languageName: node + linkType: hard + "@apache-arrow/ts@npm:^12.0.0": version: 12.0.0 resolution: "@apache-arrow/ts@npm:12.0.0" @@ -10852,6 +10877,7 @@ __metadata: resolution: "@langchain/anthropic@workspace:libs/langchain-anthropic" dependencies: "@anthropic-ai/sdk": ^0.25.2 + "@anthropic-ai/vertex-sdk": ^0.4.1 "@jest/globals": ^29.5.0 "@langchain/core": ">=0.2.21 <0.3.0" "@langchain/scripts": ">=0.1.0 <0.2.0" @@ -23262,6 +23288,7 @@ __metadata: version: 0.0.0-use.local resolution: "core_docs@workspace:docs/core_docs" dependencies: + "@anthropic-ai/vertex-sdk": ^0.4.1 "@babel/eslint-parser": ^7.18.2 "@docusaurus/core": 2.4.3 "@docusaurus/preset-classic": 2.4.3 @@ -28678,6 +28705,20 @@ __metadata: languageName: node linkType: hard +"google-auth-library@npm:^9.4.2": + version: 9.14.0 + resolution: "google-auth-library@npm:9.14.0" + dependencies: + base64-js: ^1.3.0 + ecdsa-sig-formatter: ^1.0.11 + gaxios: ^6.1.1 + gcp-metadata: ^6.1.0 + gtoken: ^7.0.0 + jws: ^4.0.0 + checksum: ff2a66e84726b3a25711a64583d86cf9e0c0df857d538ce8127822d220e81e1e7d0defcedecb2be7fc407c104bb7d5ca26a4f201bde871c8d09ef72cc83603f8 + languageName: node + linkType: hard + "google-gax@npm:^4.0.3": version: 4.3.3 resolution: "google-gax@npm:4.3.3" From 5efa3b23861bd8715d9593a57c503cf373e897d3 Mon Sep 17 00:00:00 2001 From: Jacob Lee Date: Wed, 4 Sep 2024 13:17:11 -0700 Subject: [PATCH 130/344] chore(anthropic): Release 0.2.16 (#6691) --- libs/langchain-anthropic/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libs/langchain-anthropic/package.json b/libs/langchain-anthropic/package.json index 6e037683cd15..10e91c070907 100644 --- a/libs/langchain-anthropic/package.json +++ b/libs/langchain-anthropic/package.json @@ -1,6 +1,6 @@ { "name": "@langchain/anthropic", - "version": "0.2.15", + "version": "0.2.16", "description": "Anthropic integrations for LangChain.js", "type": "module", "engines": { From 8f90583e087d281a6a3cdd337e301a92d1bbf6a3 Mon Sep 17 00:00:00 2001 From: Ben Burns <803016+benjamincburns@users.noreply.github.com> Date: Thu, 5 Sep 2024 08:19:50 +1200 Subject: [PATCH 131/344] Fix top-level dependency install for node 22+ (#6658) Co-authored-by: jacoblee93 --- libs/langchain-community/package.json | 2 +- package.json | 1 - yarn.lock | 10 +++++----- 3 files changed, 6 insertions(+), 7 deletions(-) diff --git a/libs/langchain-community/package.json b/libs/langchain-community/package.json index 767540e6f2fd..87b1223ee396 100644 --- a/libs/langchain-community/package.json +++ b/libs/langchain-community/package.json @@ -133,7 +133,7 @@ "@zilliz/milvus2-sdk-node": ">=2.3.5", "apify-client": "^2.7.1", "assemblyai": "^4.6.0", - "better-sqlite3": ">=9.4.0 <12.0.0", + "better-sqlite3": "9.5.0", "cassandra-driver": "^4.7.2", "cborg": "^4.1.1", "cheerio": "^1.0.0-rc.12", diff --git a/package.json b/package.json index fe247722ef30..2bd507f968aa 100644 --- a/package.json +++ b/package.json @@ -61,7 +61,6 @@ "typedoc-plugin-markdown@next": "patch:typedoc-plugin-markdown@npm%3A4.0.0-next.6#./.yarn/patches/typedoc-plugin-markdown-npm-4.0.0-next.6-96b4b47746.patch", "voy-search@0.6.2": "patch:voy-search@npm%3A0.6.2#./.yarn/patches/voy-search-npm-0.6.2-d4aca30a0e.patch", "@langchain/core": "workspace:*", - "better-sqlite3": "9.4.0", "protobufjs": "^7.2.5", "zod": "3.23.8" }, diff --git a/yarn.lock b/yarn.lock index 1f7b98b5f78c..140c9d4a4bf7 100644 --- a/yarn.lock +++ b/yarn.lock @@ -11261,7 +11261,7 @@ __metadata: "@zilliz/milvus2-sdk-node": ">=2.3.5" apify-client: ^2.7.1 assemblyai: ^4.6.0 - better-sqlite3: ">=9.4.0 <12.0.0" + better-sqlite3: 9.5.0 binary-extensions: ^2.2.0 cassandra-driver: ^4.7.2 cborg: ^4.1.1 @@ -21453,14 +21453,14 @@ __metadata: languageName: node linkType: hard -"better-sqlite3@npm:9.4.0": - version: 9.4.0 - resolution: "better-sqlite3@npm:9.4.0" +"better-sqlite3@npm:9.5.0": + version: 9.5.0 + resolution: "better-sqlite3@npm:9.5.0" dependencies: bindings: ^1.5.0 node-gyp: latest prebuild-install: ^7.1.1 - checksum: a1a470fae20dfba82d6e74ae90b35ea8996c60922e95574162732d6e076e84c0c90fc4ff77ab8c27554671899eb15f284e2c8de5e4ee406aa9f7eb170eca5bee + checksum: cfa56519755d6dd29ef8361c872c6f29392fcfcc44a435099eb61729aae23c4f18057972592a0b9ff0fcedc98e43db4ee7a1a2de21ab2868f8dcec4f8272ad9d languageName: node linkType: hard From 8c1396b3103ecbe77c0f52bbe7de1c2f37a0b6d2 Mon Sep 17 00:00:00 2001 From: Ben Burns <803016+benjamincburns@users.noreply.github.com> Date: Thu, 5 Sep 2024 09:47:08 +1200 Subject: [PATCH 132/344] chore(community): Bump pyodide dev version and allow peer installs up to current latest (#6680) --- langchain/package.json | 4 ++-- yarn.lock | 20 ++++++-------------- 2 files changed, 8 insertions(+), 16 deletions(-) diff --git a/langchain/package.json b/langchain/package.json index 2ec614901958..19fc15d3660f 100644 --- a/langchain/package.json +++ b/langchain/package.json @@ -670,7 +670,7 @@ "playwright": "^1.32.1", "prettier": "^2.8.3", "puppeteer": "^22.0.0", - "pyodide": "^0.24.1", + "pyodide": "^0.26.2", "redis": "^4.6.6", "release-it": "^17.6.0", "rimraf": "^5.0.1", @@ -735,7 +735,7 @@ "peggy": "^3.0.2", "playwright": "*", "puppeteer": "*", - "pyodide": "^0.24.1", + "pyodide": ">=0.24.1 <0.27.0", "redis": "*", "sonix-speech-recognition": "*", "srt-parser-2": "*", diff --git a/yarn.lock b/yarn.lock index 140c9d4a4bf7..a4ba31766201 100644 --- a/yarn.lock +++ b/yarn.lock @@ -21404,13 +21404,6 @@ __metadata: languageName: node linkType: hard -"base-64@npm:^1.0.0": - version: 1.0.0 - resolution: "base-64@npm:1.0.0" - checksum: d10b64a1fc9b2c5a5f39f1ce1e6c9d1c5b249222bbfa3a0604c592d90623caf74419983feadd8a170f27dc0c3389704f72faafa3e645aeb56bfc030c93ff074a - languageName: node - linkType: hard - "base16@npm:^1.0.0": version: 1.0.0 resolution: "base16@npm:1.0.0" @@ -32279,7 +32272,7 @@ __metadata: playwright: ^1.32.1 prettier: ^2.8.3 puppeteer: ^22.0.0 - pyodide: ^0.24.1 + pyodide: ^0.26.2 redis: ^4.6.6 release-it: ^17.6.0 rimraf: ^5.0.1 @@ -32347,7 +32340,7 @@ __metadata: peggy: ^3.0.2 playwright: "*" puppeteer: "*" - pyodide: ^0.24.1 + pyodide: ">=0.24.1 <0.27.0" redis: "*" sonix-speech-recognition: "*" srt-parser-2: "*" @@ -37332,13 +37325,12 @@ __metadata: languageName: node linkType: hard -"pyodide@npm:^0.24.1": - version: 0.24.1 - resolution: "pyodide@npm:0.24.1" +"pyodide@npm:^0.26.2": + version: 0.26.2 + resolution: "pyodide@npm:0.26.2" dependencies: - base-64: ^1.0.0 ws: ^8.5.0 - checksum: ab18e5eed3195b919c0e70b39a81bab0449ab56e42f28e8635664e688dba732da060492505719c3cb477302979791dbcc1df09ff39dc9e0816064b69818f283d + checksum: f8b11470a69f82609af9e1a1b76905dfd5baa42f86ce5488c44b82271af46252006bb73639f8d89aee11b9fb07d1e9de037ff5472aa76f1b4ff4e9fe09d7e839 languageName: node linkType: hard From 8365a7f60c7d6e3bc3b44c2e19c01742ab0b7c87 Mon Sep 17 00:00:00 2001 From: Kerem Nalbant <43813768+keremnalbant@users.noreply.github.com> Date: Thu, 5 Sep 2024 01:19:23 +0300 Subject: [PATCH 133/344] fix(community): Add support for Bedrock cross-region inference models (#6682) Co-authored-by: jacoblee93 --- .../src/chat_models/bedrock/web.ts | 60 ++++++++++++++----- .../src/llms/bedrock/web.ts | 56 +++++++++++++---- .../src/llms/tests/bedrock.int.test.ts | 22 +++++++ 3 files changed, 112 insertions(+), 26 deletions(-) diff --git a/libs/langchain-community/src/chat_models/bedrock/web.ts b/libs/langchain-community/src/chat_models/bedrock/web.ts index a2e78d67e711..69a0bb42eb8b 100644 --- a/libs/langchain-community/src/chat_models/bedrock/web.ts +++ b/libs/langchain-community/src/chat_models/bedrock/web.ts @@ -52,6 +52,28 @@ type AnthropicTool = Record; type BedrockChatToolType = BindToolsInput | AnthropicTool; +const AWS_REGIONS = [ + "us", + "sa", + "me", + "il", + "eu", + "cn", + "ca", + "ap", + "af", + "us-gov", +]; + +const ALLOWED_MODEL_PROVIDERS = [ + "ai21", + "anthropic", + "amazon", + "cohere", + "meta", + "mistral", +]; + const PRELUDE_TOTAL_LENGTH_BYTES = 4; function convertOneMessageToText( @@ -473,6 +495,8 @@ export class BedrockChat { model = "amazon.titan-tg1-large"; + modelProvider: string; + region: string; credentials: CredentialType; @@ -545,17 +569,11 @@ export class BedrockChat super(fields ?? {}); this.model = fields?.model ?? this.model; - const allowedModels = [ - "ai21", - "anthropic", - "amazon", - "cohere", - "meta", - "mistral", - ]; - if (!allowedModels.includes(this.model.split(".")[0])) { + this.modelProvider = getModelProvider(this.model); + + if (!ALLOWED_MODEL_PROVIDERS.includes(this.modelProvider)) { throw new Error( - `Unknown model: '${this.model}', only these are supported: ${allowedModels}` + `Unknown model provider: '${this.modelProvider}', only these are supported: ${ALLOWED_MODEL_PROVIDERS}` ); } const region = @@ -655,7 +673,7 @@ export class BedrockChat const service = "bedrock-runtime"; const endpointHost = this.endpointHost ?? `${service}.${this.region}.amazonaws.com`; - const provider = this.model.split(".")[0]; + const provider = this.modelProvider; const response = await this._signedFetch(messages, options, { bedrockMethod: "invoke", endpointHost, @@ -776,7 +794,7 @@ export class BedrockChat options: this["ParsedCallOptions"], runManager?: CallbackManagerForLLMRun ): AsyncGenerator { - const provider = this.model.split(".")[0]; + const provider = this.modelProvider; const service = "bedrock-runtime"; const endpointHost = @@ -956,7 +974,7 @@ export class BedrockChat BaseMessageChunk, this["ParsedCallOptions"] > { - const provider = this.model.split(".")[0]; + const provider = this.modelProvider; if (provider !== "anthropic") { throw new Error( "Currently, tool calling through Bedrock is only supported for Anthropic models." @@ -977,7 +995,7 @@ function isChatGenerationChunk( } function canUseMessagesApi(model: string): boolean { - const modelProviderName = model.split(".")[0]; + const modelProviderName = getModelProvider(model); if ( modelProviderName === "anthropic" && @@ -999,6 +1017,20 @@ function canUseMessagesApi(model: string): boolean { return false; } +function isInferenceModel(modelId: string): boolean { + const parts = modelId.split("."); + return AWS_REGIONS.some((region) => parts[0] === region); +} + +function getModelProvider(modelId: string): string { + const parts = modelId.split("."); + if (isInferenceModel(modelId)) { + return parts[1]; + } else { + return parts[0]; + } +} + /** * @deprecated Use `BedrockChat` instead. */ diff --git a/libs/langchain-community/src/llms/bedrock/web.ts b/libs/langchain-community/src/llms/bedrock/web.ts index 8f7a5791cda9..e2dc5d31df92 100644 --- a/libs/langchain-community/src/llms/bedrock/web.ts +++ b/libs/langchain-community/src/llms/bedrock/web.ts @@ -17,6 +17,28 @@ import { } from "../../utils/bedrock/index.js"; import type { SerializedFields } from "../../load/map_keys.js"; +const AWS_REGIONS = [ + "us", + "sa", + "me", + "il", + "eu", + "cn", + "ca", + "ap", + "af", + "us-gov", +]; + +const ALLOWED_MODEL_PROVIDERS = [ + "ai21", + "anthropic", + "amazon", + "cohere", + "meta", + "mistral", +]; + const PRELUDE_TOTAL_LENGTH_BYTES = 4; /** @@ -31,6 +53,8 @@ const PRELUDE_TOTAL_LENGTH_BYTES = 4; export class Bedrock extends LLM implements BaseBedrockInput { model = "amazon.titan-tg1-large"; + modelProvider: string; + region: string; credentials: CredentialType; @@ -84,17 +108,11 @@ export class Bedrock extends LLM implements BaseBedrockInput { super(fields ?? {}); this.model = fields?.model ?? this.model; - const allowedModels = [ - "ai21", - "anthropic", - "amazon", - "cohere", - "meta", - "mistral", - ]; - if (!allowedModels.includes(this.model.split(".")[0])) { + this.modelProvider = getModelProvider(this.model); + + if (!ALLOWED_MODEL_PROVIDERS.includes(this.modelProvider)) { throw new Error( - `Unknown model: '${this.model}', only these are supported: ${allowedModels}` + `Unknown model provider: '${this.modelProvider}', only these are supported: ${ALLOWED_MODEL_PROVIDERS}` ); } const region = @@ -141,7 +159,7 @@ export class Bedrock extends LLM implements BaseBedrockInput { const service = "bedrock-runtime"; const endpointHost = this.endpointHost ?? `${service}.${this.region}.amazonaws.com`; - const provider = this.model.split(".")[0]; + const provider = this.modelProvider; if (this.streaming) { const stream = this._streamResponseChunks(prompt, options, runManager); let finalResult: GenerationChunk | undefined; @@ -246,7 +264,7 @@ export class Bedrock extends LLM implements BaseBedrockInput { options: this["ParsedCallOptions"], runManager?: CallbackManagerForLLMRun ): AsyncGenerator { - const provider = this.model.split(".")[0]; + const provider = this.modelProvider; const bedrockMethod = provider === "anthropic" || provider === "cohere" || @@ -371,3 +389,17 @@ export class Bedrock extends LLM implements BaseBedrockInput { }; } } + +function isInferenceModel(modelId: string): boolean { + const parts = modelId.split("."); + return AWS_REGIONS.some((region) => parts[0] === region); +} + +function getModelProvider(modelId: string): string { + const parts = modelId.split("."); + if (isInferenceModel(modelId)) { + return parts[1]; + } else { + return parts[0]; + } +} diff --git a/libs/langchain-community/src/llms/tests/bedrock.int.test.ts b/libs/langchain-community/src/llms/tests/bedrock.int.test.ts index b05c1c1f962c..9be73e5c8253 100644 --- a/libs/langchain-community/src/llms/tests/bedrock.int.test.ts +++ b/libs/langchain-community/src/llms/tests/bedrock.int.test.ts @@ -180,3 +180,25 @@ test("Test Bedrock LLM streaming: Claude-v2", async () => { } expect(chunks.length).toBeGreaterThan(1); }); + +test("Test Bedrock LLM: Inference Models", async () => { + const region = process.env.BEDROCK_AWS_REGION!; + const model = "eu.anthropic.claude-3-5-sonnet-20240620-v1:0"; + const prompt = "Human: What is your name?\n\nAssistant:"; + + const bedrock = new Bedrock({ + maxTokens: 20, + region, + model, + maxRetries: 0, + credentials: { + accessKeyId: process.env.BEDROCK_AWS_ACCESS_KEY_ID!, + secretAccessKey: process.env.BEDROCK_AWS_SECRET_ACCESS_KEY!, + sessionToken: process.env.BEDROCK_AWS_SESSION_TOKEN, + }, + }); + + const res = await bedrock.invoke(prompt); + expect(typeof res).toBe("string"); + // console.log(res); +}); From 3c007872514700562f62fe56ad2e216753988428 Mon Sep 17 00:00:00 2001 From: Jacob Lee Date: Wed, 4 Sep 2024 15:41:20 -0700 Subject: [PATCH 134/344] chore(community): Release 0.2.32 (#6692) --- libs/langchain-community/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libs/langchain-community/package.json b/libs/langchain-community/package.json index 87b1223ee396..a1276dab1c0f 100644 --- a/libs/langchain-community/package.json +++ b/libs/langchain-community/package.json @@ -1,6 +1,6 @@ { "name": "@langchain/community", - "version": "0.2.31", + "version": "0.2.32", "description": "Third-party integrations for LangChain.js", "type": "module", "engines": { From 24b40fb93cc3e47f014577833f5036cf72770eab Mon Sep 17 00:00:00 2001 From: jacoblee93 Date: Wed, 4 Sep 2024 15:45:39 -0700 Subject: [PATCH 135/344] Release 0.2.18 --- langchain/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/langchain/package.json b/langchain/package.json index 19fc15d3660f..91c99b175d6e 100644 --- a/langchain/package.json +++ b/langchain/package.json @@ -1,6 +1,6 @@ { "name": "langchain", - "version": "0.2.17", + "version": "0.2.18", "description": "Typescript bindings for langchain", "type": "module", "engines": { From 01a94728eab97e63e748b3c40419723292abd5d9 Mon Sep 17 00:00:00 2001 From: Roman Date: Thu, 5 Sep 2024 01:10:31 +0200 Subject: [PATCH 136/344] fix(mongodb): mongodb storage mget must return undefined when key not exist (#6445) Co-authored-by: Roman Tyshyk Co-authored-by: jacoblee93 --- libs/langchain-mongodb/src/storage.ts | 15 ++++++--- .../src/tests/storage.int.test.ts | 32 +++++++++++-------- 2 files changed, 30 insertions(+), 17 deletions(-) diff --git a/libs/langchain-mongodb/src/storage.ts b/libs/langchain-mongodb/src/storage.ts index 528afbe5dbc8..0b3fd38b0174 100644 --- a/libs/langchain-mongodb/src/storage.ts +++ b/libs/langchain-mongodb/src/storage.ts @@ -35,7 +35,7 @@ export interface MongoDBStoreInput { * const store = new MongoDBStore({ * collection, * }); - * + * * const docs = [ * [uuidv4(), "Dogs are tough."], * [uuidv4(), "Cats are tough."], @@ -97,11 +97,18 @@ export class MongoDBStore extends BaseStore { .toArray(); const encoder = new TextEncoder(); - return retrievedValues.map((value) => { - if (!("value" in value)) { + const valueMap = new Map( + retrievedValues.map((item) => [item[this.primaryKey], item]) + ); + + return prefixedKeys.map((prefixedKey) => { + const value = valueMap.get(prefixedKey); + + if (!value) { return undefined; } - if (value === undefined || value === null) { + + if (!("value" in value)) { return undefined; } else if (typeof value.value === "object") { return encoder.encode(JSON.stringify(value.value)); diff --git a/libs/langchain-mongodb/src/tests/storage.int.test.ts b/libs/langchain-mongodb/src/tests/storage.int.test.ts index 93aeb03a9f9d..a161f612f65a 100644 --- a/libs/langchain-mongodb/src/tests/storage.int.test.ts +++ b/libs/langchain-mongodb/src/tests/storage.int.test.ts @@ -43,18 +43,22 @@ test("MongoDBStore can set and retrieve", async () => { encoder.encode(doc[1]), ]); await store.mset(docsAsKVPairs); - const retrievedDocs = (await store.mget(docs.map((doc) => doc[0]))).flatMap( - (doc) => { - if (doc !== undefined) { - const decodedDoc = decoder.decode(doc); - const parsedDoc = JSON.parse(decodedDoc); - return [parsedDoc]; - } - return []; - } - ); - - expect(retrievedDocs.sort()).toEqual(docs.map((doc) => doc[1]).sort()); + + const keysToRetrieve = docs.map((doc) => doc[0]); + keysToRetrieve.unshift("nonexistent_key_0"); + keysToRetrieve.push("nonexistent_key_3"); + + const retrievedDocs = await store.mget(keysToRetrieve); + expect(retrievedDocs.length).toBe(keysToRetrieve.length); + // Check that the first item is undefined (nonexistent_key_0) + expect(retrievedDocs[0]).toBeUndefined(); + + // Check that the second and third items match the original docs + expect(decoder.decode(retrievedDocs[1])).toBe(docs[0][1]); + expect(decoder.decode(retrievedDocs[2])).toBe(docs[1][1]); + + // Check that the last item is undefined (nonexistent_key_1) + expect(retrievedDocs[retrievedDocs.length - 1]).toBeUndefined(); } finally { const keys = store.yieldKeys(); const yieldedKeys = []; @@ -104,7 +108,9 @@ test("MongoDBStore can delete", async () => { const retrievedDocs = await store.mget(docs.map((doc) => doc[0])); - expect(retrievedDocs.length).toBe(0); + expect(retrievedDocs.length).toBe(2); + const everyValueUndefined = retrievedDocs.every((v) => v === undefined); + expect(everyValueUndefined).toBe(true); } finally { const keys = store.yieldKeys(); const yieldedKeys = []; From 15b591607dfc14c78d62e5ef4eef24107f168a34 Mon Sep 17 00:00:00 2001 From: Jacob Lee Date: Wed, 4 Sep 2024 16:12:49 -0700 Subject: [PATCH 137/344] chore(mongodb): Release 0.0.6 (#6694) --- libs/langchain-mongodb/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libs/langchain-mongodb/package.json b/libs/langchain-mongodb/package.json index 0cca4d7d6006..a920176fa2e2 100644 --- a/libs/langchain-mongodb/package.json +++ b/libs/langchain-mongodb/package.json @@ -1,6 +1,6 @@ { "name": "@langchain/mongodb", - "version": "0.0.5", + "version": "0.0.6", "description": "Sample integration for LangChain.js", "type": "module", "engines": { From e787d821b091fb373c48c9a1c07355b0b65b7ab2 Mon Sep 17 00:00:00 2001 From: Jacob Lee Date: Wed, 4 Sep 2024 16:30:41 -0700 Subject: [PATCH 138/344] fix(openai): Fix type import issue (#6695) --- libs/langchain-openai/package.json | 2 +- libs/langchain-openai/src/chat_models.ts | 4 ++-- yarn.lock | 28 +++++++++++++++++++----- 3 files changed, 26 insertions(+), 8 deletions(-) diff --git a/libs/langchain-openai/package.json b/libs/langchain-openai/package.json index f4d9754b0877..0bd159f1b16f 100644 --- a/libs/langchain-openai/package.json +++ b/libs/langchain-openai/package.json @@ -37,7 +37,7 @@ "dependencies": { "@langchain/core": ">=0.2.26 <0.3.0", "js-tiktoken": "^1.0.12", - "openai": "^4.55.0", + "openai": "^4.57.3", "zod": "^3.22.4", "zod-to-json-schema": "^3.22.3" }, diff --git a/libs/langchain-openai/src/chat_models.ts b/libs/langchain-openai/src/chat_models.ts index f2219bd5a5d1..cf8cdfcd5e05 100644 --- a/libs/langchain-openai/src/chat_models.ts +++ b/libs/langchain-openai/src/chat_models.ts @@ -61,7 +61,6 @@ import type { ResponseFormatJSONObject, ResponseFormatJSONSchema, } from "openai/resources/shared"; -import { ParsedChatCompletion } from "openai/resources/beta/chat/completions.mjs"; import type { AzureOpenAIInput, OpenAICallOptions, @@ -1615,7 +1614,8 @@ export class ChatOpenAI< async betaParsedCompletionWithRetry( request: OpenAIClient.Chat.ChatCompletionCreateParamsNonStreaming, options?: OpenAICoreRequestOptions - ): Promise> { + // Avoid relying importing a beta type with no official entrypoint + ): Promise> { const requestOptions = this._getClientOptions(options); return this.caller.call(async () => { try { diff --git a/yarn.lock b/yarn.lock index a4ba31766201..edaa48057750 100644 --- a/yarn.lock +++ b/yarn.lock @@ -12300,7 +12300,7 @@ __metadata: jest: ^29.5.0 jest-environment-node: ^29.6.4 js-tiktoken: ^1.0.12 - openai: ^4.55.0 + openai: ^4.57.3 prettier: ^2.8.3 release-it: ^17.6.0 rimraf: ^5.0.1 @@ -19003,6 +19003,13 @@ __metadata: languageName: node linkType: hard +"@types/qs@npm:^6.9.15": + version: 6.9.15 + resolution: "@types/qs@npm:6.9.15" + checksum: 97d8208c2b82013b618e7a9fc14df6bd40a73e1385ac479b6896bafc7949a46201c15f42afd06e86a05e914f146f495f606b6fb65610cc60cf2e0ff743ec38a2 + languageName: node + linkType: hard + "@types/range-parser@npm:*": version: 1.2.4 resolution: "@types/range-parser@npm:1.2.4" @@ -35228,17 +35235,19 @@ __metadata: languageName: node linkType: hard -"openai@npm:^4.55.0": - version: 4.55.0 - resolution: "openai@npm:4.55.0" +"openai@npm:^4.57.3": + version: 4.57.3 + resolution: "openai@npm:4.57.3" dependencies: "@types/node": ^18.11.18 "@types/node-fetch": ^2.6.4 + "@types/qs": ^6.9.15 abort-controller: ^3.0.0 agentkeepalive: ^4.2.1 form-data-encoder: 1.7.2 formdata-node: ^4.3.2 node-fetch: ^2.6.7 + qs: ^6.10.3 peerDependencies: zod: ^3.23.8 peerDependenciesMeta: @@ -35246,7 +35255,7 @@ __metadata: optional: true bin: openai: bin/cli - checksum: b2b1daa976516262e08e182ee982976a1dc615eebd250bbd71f4122740ebeeb207a20af6d35c718b67f1c3457196b524667a0c7fa417ab4e119020b5c1f5cd74 + checksum: 6e8cef99975af5fd8e9a06685f05396a6fabecda38bd77fa62db4b7ea9bdfa0b4c762c5f74e99e42212af81f74f50748c5034bf78c9abcf74cc6eb984f3dcffa languageName: node linkType: hard @@ -37361,6 +37370,15 @@ __metadata: languageName: node linkType: hard +"qs@npm:^6.10.3": + version: 6.13.0 + resolution: "qs@npm:6.13.0" + dependencies: + side-channel: ^1.0.6 + checksum: e9404dc0fc2849245107108ce9ec2766cde3be1b271de0bf1021d049dc5b98d1a2901e67b431ac5509f865420a7ed80b7acb3980099fe1c118a1c5d2e1432ad8 + languageName: node + linkType: hard + "querystringify@npm:^2.1.1": version: 2.2.0 resolution: "querystringify@npm:2.2.0" From 2ec5184417208ace6a5e2005515484a45cb5adf6 Mon Sep 17 00:00:00 2001 From: Ion Prodan Date: Thu, 5 Sep 2024 02:30:47 +0300 Subject: [PATCH 139/344] feat(openai): baseUrl for DallEAPIWrapper (#6685) --- libs/langchain-openai/src/tools/dalle.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/libs/langchain-openai/src/tools/dalle.ts b/libs/langchain-openai/src/tools/dalle.ts index 35d405807cc2..d3d45c6dba90 100644 --- a/libs/langchain-openai/src/tools/dalle.ts +++ b/libs/langchain-openai/src/tools/dalle.ts @@ -80,6 +80,10 @@ export interface DallEAPIWrapperParams extends ToolParams { * The organization to use */ organization?: string; + /** + * The base URL of the OpenAI API. + */ + baseUrl?: string; } /** @@ -141,6 +145,7 @@ export class DallEAPIWrapper extends Tool { apiKey: openAIApiKey, organization, dangerouslyAllowBrowser: true, + baseUrl: fields?.baseUrl, }; this.client = new OpenAIClient(clientConfig); this.model = fields?.model ?? fields?.modelName ?? this.model; From d09af37072c542ab7f416f6de01f9a1fabce524d Mon Sep 17 00:00:00 2001 From: Jacob Lee Date: Wed, 4 Sep 2024 16:36:29 -0700 Subject: [PATCH 140/344] chore(openai): Release 0.2.10 (#6697) --- libs/langchain-openai/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libs/langchain-openai/package.json b/libs/langchain-openai/package.json index 0bd159f1b16f..838728fad470 100644 --- a/libs/langchain-openai/package.json +++ b/libs/langchain-openai/package.json @@ -1,6 +1,6 @@ { "name": "@langchain/openai", - "version": "0.2.9", + "version": "0.2.10", "description": "OpenAI integrations for LangChain.js", "type": "module", "engines": { From 938fa72cfa17f02bae4d13b79c79938a3211db41 Mon Sep 17 00:00:00 2001 From: Brace Sproul Date: Wed, 4 Sep 2024 16:44:11 -0700 Subject: [PATCH 141/344] community[patch]: Relax webllm peer dep (#6696) --- libs/langchain-community/package.json | 4 ++-- yarn.lock | 12 ++++++------ 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/libs/langchain-community/package.json b/libs/langchain-community/package.json index a1276dab1c0f..988f307255d0 100644 --- a/libs/langchain-community/package.json +++ b/libs/langchain-community/package.json @@ -83,7 +83,7 @@ "@langchain/standard-tests": "0.0.0", "@layerup/layerup-security": "^1.5.12", "@mendable/firecrawl-js": "^0.0.36", - "@mlc-ai/web-llm": "0.2.46", + "@mlc-ai/web-llm": ">=0.2.62 <0.3.0", "@mozilla/readability": "^0.4.4", "@neondatabase/serverless": "^0.9.1", "@notionhq/client": "^2.2.10", @@ -242,7 +242,7 @@ "@langchain/langgraph": "*", "@layerup/layerup-security": "^1.5.12", "@mendable/firecrawl-js": "^0.0.13", - "@mlc-ai/web-llm": "0.2.46", + "@mlc-ai/web-llm": "*", "@mozilla/readability": "*", "@neondatabase/serverless": "*", "@notionhq/client": "^2.2.10", diff --git a/yarn.lock b/yarn.lock index edaa48057750..6a779f772556 100644 --- a/yarn.lock +++ b/yarn.lock @@ -11211,7 +11211,7 @@ __metadata: "@langchain/standard-tests": 0.0.0 "@layerup/layerup-security": ^1.5.12 "@mendable/firecrawl-js": ^0.0.36 - "@mlc-ai/web-llm": 0.2.46 + "@mlc-ai/web-llm": ">=0.2.62 <0.3.0" "@mozilla/readability": ^0.4.4 "@neondatabase/serverless": ^0.9.1 "@notionhq/client": ^2.2.10 @@ -11378,7 +11378,7 @@ __metadata: "@langchain/langgraph": "*" "@layerup/layerup-security": ^1.5.12 "@mendable/firecrawl-js": ^0.0.13 - "@mlc-ai/web-llm": 0.2.46 + "@mlc-ai/web-llm": "*" "@mozilla/readability": "*" "@neondatabase/serverless": "*" "@notionhq/client": ^2.2.10 @@ -12776,12 +12776,12 @@ __metadata: languageName: node linkType: hard -"@mlc-ai/web-llm@npm:0.2.46": - version: 0.2.46 - resolution: "@mlc-ai/web-llm@npm:0.2.46" +"@mlc-ai/web-llm@npm:>=0.2.62 <0.3.0": + version: 0.2.62 + resolution: "@mlc-ai/web-llm@npm:0.2.62" dependencies: loglevel: ^1.9.1 - checksum: 09c83a45d7f9351ae492d8704fe580868d0b46b640eca232ebc76d552f2ffad031c9c504a0d29f69122029478af270eeeda0800e7fb032b00c11dc1632e1ae11 + checksum: 7ccb0842e3fe83156406e61e0172f2f07115c77669b37729499f52c120cc33acd000fc4619b30fb67ca7712246c9722af65e4fe208c2f4546e2882297d53293c languageName: node linkType: hard From 68fbc883f8c3590cb61b0a0df16149f7b2ec122a Mon Sep 17 00:00:00 2001 From: ikalachy Date: Thu, 5 Sep 2024 20:19:47 +0300 Subject: [PATCH 142/344] langchain[patch]: Take all required fields into account for OpenAPI chain (#6700) Co-authored-by: Brace Sproul --- .../src/chains/openai_functions/openapi.ts | 6 +++++- .../openai_functions/tests/openapi.test.ts | 19 +++++++++++++++++++ 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/langchain/src/chains/openai_functions/openapi.ts b/langchain/src/chains/openai_functions/openapi.ts index 568ff7635a8c..8b867856f3d8 100644 --- a/langchain/src/chains/openai_functions/openapi.ts +++ b/langchain/src/chains/openai_functions/openapi.ts @@ -175,7 +175,11 @@ export function convertOpenAPISchemaToJSONSchema( openAPIProperty, spec ); - if (openAPIProperty.required && jsonSchema.required !== undefined) { + if ( + (openAPIProperty.required || + schema.required?.includes(propertyName)) && + jsonSchema.required !== undefined + ) { jsonSchema.required.push(propertyName); } return jsonSchema; diff --git a/langchain/src/chains/openai_functions/tests/openapi.test.ts b/langchain/src/chains/openai_functions/tests/openapi.test.ts index d18f755613ce..93243f163b91 100644 --- a/langchain/src/chains/openai_functions/tests/openapi.test.ts +++ b/langchain/src/chains/openai_functions/tests/openapi.test.ts @@ -46,6 +46,19 @@ test("Test convert OpenAPI params to JSON Schema", async () => { }, }, }, + { + name: "objectParamWithRequiredFields", + in: "query", + schema: { + type: "object", + required: ["fooRequired"], + properties: { + fooRequired: { + type: "string", + }, + }, + }, + }, { name: "stringArrayParam", in: "query", @@ -195,6 +208,12 @@ test("Test convert OpenAPI params to JSON Schema", async () => { expectType("string", typedObjectParamSchema.properties.foo); expectType("number", typedObjectParamSchema.properties.bar); + const objectParamWithRequiredFieldSchema = convertOpenAPISchemaToJSONSchema( + getParamSchema(createWidget, "objectParamWithRequiredFields"), + spec + ) as JsonSchema7ObjectType; + expect(objectParamWithRequiredFieldSchema.required).toContain("fooRequired"); + const stringArrayParamSchema = convertOpenAPISchemaToJSONSchema( getParamSchema(createWidget, "stringArrayParam"), spec From a3d6aee5639f24563a476434cd9661980632c083 Mon Sep 17 00:00:00 2001 From: Brace Sproul Date: Fri, 6 Sep 2024 14:27:59 -0700 Subject: [PATCH 143/344] docs[minor]: Add state of agents survey to docs announcement bar (#6710) --- docs/core_docs/.gitignore | 12 ++++++------ docs/core_docs/docusaurus.config.js | 4 +++- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/docs/core_docs/.gitignore b/docs/core_docs/.gitignore index 39187a290670..ca1dd4efb878 100644 --- a/docs/core_docs/.gitignore +++ b/docs/core_docs/.gitignore @@ -240,18 +240,18 @@ docs/integrations/vectorstores/elasticsearch.md docs/integrations/vectorstores/elasticsearch.mdx docs/integrations/vectorstores/chroma.md docs/integrations/vectorstores/chroma.mdx -docs/integrations/toolkits/vectorstore.md -docs/integrations/toolkits/vectorstore.mdx -docs/integrations/toolkits/sql.md -docs/integrations/toolkits/sql.mdx -docs/integrations/toolkits/openapi.md -docs/integrations/toolkits/openapi.mdx docs/integrations/tools/tavily_search.md docs/integrations/tools/tavily_search.mdx docs/integrations/tools/exa_search.md docs/integrations/tools/exa_search.mdx docs/integrations/tools/duckduckgo_search.md docs/integrations/tools/duckduckgo_search.mdx +docs/integrations/toolkits/vectorstore.md +docs/integrations/toolkits/vectorstore.mdx +docs/integrations/toolkits/sql.md +docs/integrations/toolkits/sql.mdx +docs/integrations/toolkits/openapi.md +docs/integrations/toolkits/openapi.mdx docs/integrations/text_embedding/togetherai.md docs/integrations/text_embedding/togetherai.mdx docs/integrations/text_embedding/openai.md diff --git a/docs/core_docs/docusaurus.config.js b/docs/core_docs/docusaurus.config.js index b368b57a9d77..6e56cf2d1959 100644 --- a/docs/core_docs/docusaurus.config.js +++ b/docs/core_docs/docusaurus.config.js @@ -137,8 +137,10 @@ const config = { ({ announcementBar: { content: - 'LangChain 0.2 is out! Leave feedback on the v0.2 docs
here. You can view the v0.1 docs here.', + 'Share your thoughts on AI agents. Take the 3-min survey.', isCloseable: true, + backgroundColor: "rgba(53, 151, 147, 0.1)", + textColor: "rgb(53, 151, 147)", }, prism: { theme: { From 360dc174d2ee0d894be6eb586b063025678af471 Mon Sep 17 00:00:00 2001 From: Brace Sproul Date: Fri, 6 Sep 2024 16:51:42 -0700 Subject: [PATCH 144/344] anthropic[patch]: Fix passing cache control through in messages (#6711) --- .../src/tests/chat_models.int.test.ts | 46 ++++++++++++++++++- .../src/utils/message_inputs.ts | 6 +++ 2 files changed, 50 insertions(+), 2 deletions(-) diff --git a/libs/langchain-anthropic/src/tests/chat_models.int.test.ts b/libs/langchain-anthropic/src/tests/chat_models.int.test.ts index d6864e92aa3d..7d823727fc17 100644 --- a/libs/langchain-anthropic/src/tests/chat_models.int.test.ts +++ b/libs/langchain-anthropic/src/tests/chat_models.int.test.ts @@ -393,8 +393,7 @@ test("id is supplied when streaming", async () => { expect(finalChunk.id).not.toEqual(""); }); -test("system prompt caching", async () => { - const CACHED_TEXT = `## Components +const CACHED_TEXT = `## Components LangChain provides standard, extendable interfaces and external integrations for various components useful for building with LLMs. Some components LangChain implements, some components we rely on third-party integrations for, and others are a mix. @@ -655,6 +654,7 @@ LangChain has many different types of output parsers. This is a list of output p The current date is ${new Date().toISOString()}`; +test("system prompt caching", async () => { const model = new ChatAnthropic({ model: "claude-3-haiku-20240307", clientOptions: { @@ -749,3 +749,45 @@ test.skip("Test ChatAnthropic with custom client", async () => { // console.log({ res }); expect(res.response_metadata.usage).toBeDefined(); }); + +test("human message caching", async () => { + const model = new ChatAnthropic({ + model: "claude-3-haiku-20240307", + clientOptions: { + defaultHeaders: { + "anthropic-beta": "prompt-caching-2024-07-31", + }, + }, + }); + + const messages = [ + new SystemMessage({ + content: [ + { + type: "text", + text: `You are a pirate. Always respond in pirate dialect.\nUse the following as context when answering questions: ${CACHED_TEXT}`, + }, + ], + }), + new HumanMessage({ + content: [ + { + type: "text", + text: "What types of messages are supported in LangChain?", + cache_control: { type: "ephemeral" }, + }, + ], + }), + ]; + + const res = await model.invoke(messages); + expect( + res.response_metadata.usage.cache_creation_input_tokens + ).toBeGreaterThan(0); + expect(res.response_metadata.usage.cache_read_input_tokens).toBe(0); + const res2 = await model.invoke(messages); + expect(res2.response_metadata.usage.cache_creation_input_tokens).toBe(0); + expect(res2.response_metadata.usage.cache_read_input_tokens).toBeGreaterThan( + 0 + ); +}); diff --git a/libs/langchain-anthropic/src/utils/message_inputs.ts b/libs/langchain-anthropic/src/utils/message_inputs.ts index b6fe6523df4a..b32786e43f02 100644 --- a/libs/langchain-anthropic/src/utils/message_inputs.ts +++ b/libs/langchain-anthropic/src/utils/message_inputs.ts @@ -122,6 +122,9 @@ function _formatContent(content: MessageContent) { return content; } else { const contentBlocks = content.map((contentPart) => { + const cacheControl = + "cache_control" in contentPart ? contentPart.cache_control : undefined; + if (contentPart.type === "image_url") { let source; if (typeof contentPart.image_url === "string") { @@ -132,6 +135,7 @@ function _formatContent(content: MessageContent) { return { type: "image" as const, // Explicitly setting the type as "image" source, + ...(cacheControl ? { cache_control: cacheControl } : {}), }; } else if ( textTypes.find((t) => t === contentPart.type) && @@ -141,6 +145,7 @@ function _formatContent(content: MessageContent) { return { type: "text" as const, // Explicitly setting the type as "text" text: contentPart.text, + ...(cacheControl ? { cache_control: cacheControl } : {}), }; } else if (toolTypes.find((t) => t === contentPart.type)) { const contentPartCopy = { ...contentPart }; @@ -167,6 +172,7 @@ function _formatContent(content: MessageContent) { // TODO: Fix when SDK types are fixed return { ...contentPartCopy, + ...(cacheControl ? { cache_control: cacheControl } : {}), // eslint-disable-next-line @typescript-eslint/no-explicit-any } as any; } else { From 3b9d7a1ba57d0c16dc1eaa86955a55ad24eaa876 Mon Sep 17 00:00:00 2001 From: Brace Sproul Date: Fri, 6 Sep 2024 16:56:26 -0700 Subject: [PATCH 145/344] anthropic[minor]: Allow different tool types to be bound (#6712) --- libs/langchain-anthropic/src/chat_models.ts | 61 +++++++++---------- .../src/tests/chat_models-tools.int.test.ts | 27 ++++++++ 2 files changed, 55 insertions(+), 33 deletions(-) diff --git a/libs/langchain-anthropic/src/chat_models.ts b/libs/langchain-anthropic/src/chat_models.ts index 1d561194faa7..c8edf3c17d0d 100644 --- a/libs/langchain-anthropic/src/chat_models.ts +++ b/libs/langchain-anthropic/src/chat_models.ts @@ -14,7 +14,6 @@ import { import { type StructuredOutputMethodOptions, type BaseLanguageModelInput, - type ToolDefinition, isOpenAITool, } from "@langchain/core/language_models/base"; import { zodToJsonSchema } from "zod-to-json-schema"; @@ -682,7 +681,6 @@ export class ChatAnthropicMessages< * * @param {ChatAnthropicCallOptions["tools"]} tools The tools to format * @returns {AnthropicTool[] | undefined} The formatted tools, or undefined if none are passed. - * @throws {Error} If a mix of AnthropicTools and StructuredTools are passed. */ formatStructuredToolToAnthropic( tools: ChatAnthropicCallOptions["tools"] @@ -690,37 +688,34 @@ export class ChatAnthropicMessages< if (!tools || !tools.length) { return undefined; } - - // eslint-disable-next-line @typescript-eslint/no-explicit-any - if ((tools as any[]).every((tool) => isAnthropicTool(tool))) { - // If the tool is already an anthropic tool, return it - return tools as AnthropicTool[]; - } - - // eslint-disable-next-line @typescript-eslint/no-explicit-any - if ((tools as any[]).every((tool) => isOpenAITool(tool))) { - // Formatted as OpenAI tool, convert to Anthropic tool - return (tools as ToolDefinition[]).map((tc) => ({ - name: tc.function.name, - description: tc.function.description, - input_schema: tc.function.parameters as AnthropicTool.InputSchema, - })); - } - - // eslint-disable-next-line @typescript-eslint/no-explicit-any - if ((tools as any[]).some((tool) => isAnthropicTool(tool))) { - throw new Error(`Can not pass in a mix of tool schemas to ChatAnthropic`); - } - - if (tools.every(isLangChainTool)) { - return tools.map((t) => ({ - name: t.name, - description: t.description, - input_schema: zodToJsonSchema(t.schema) as AnthropicTool.InputSchema, - })); - } - - throw new Error("Unsupported tool type passed to ChatAnthropic"); + return tools.map((tool) => { + if (isAnthropicTool(tool)) { + return tool; + } + if (isOpenAITool(tool)) { + return { + name: tool.function.name, + description: tool.function.description, + input_schema: tool.function.parameters as AnthropicTool.InputSchema, + }; + } + if (isLangChainTool(tool)) { + return { + name: tool.name, + description: tool.description, + input_schema: zodToJsonSchema( + tool.schema + ) as AnthropicTool.InputSchema, + }; + } + throw new Error( + `Unknown tool type passed to ChatAnthropic: ${JSON.stringify( + tool, + null, + 2 + )}` + ); + }); } override bindTools( diff --git a/libs/langchain-anthropic/src/tests/chat_models-tools.int.test.ts b/libs/langchain-anthropic/src/tests/chat_models-tools.int.test.ts index 7cd53c99a8b6..645f9304b07e 100644 --- a/libs/langchain-anthropic/src/tests/chat_models-tools.int.test.ts +++ b/libs/langchain-anthropic/src/tests/chat_models-tools.int.test.ts @@ -461,3 +461,30 @@ test("streaming with structured output", async () => { } expect(typeof finalChunk2).toEqual("object"); }); + +test("Can bound and invoke different tool types", async () => { + const langchainTool = { + name: "get_weather_lc", + description: "Get the weather of a specific location.", + schema: zodSchema, + }; + const openaiTool = { + type: "function", + function: { + name: "get_weather_oai", + description: "Get the weather of a specific location.", + parameters: zodToJsonSchema(zodSchema), + }, + }; + const anthropicTool = { + name: "get_weather_ant", + description: "Get the weather of a specific location.", + input_schema: zodToJsonSchema(zodSchema), + }; + const tools = [langchainTool, openaiTool, anthropicTool]; + const modelWithTools = model.bindTools(tools); + const result = await modelWithTools.invoke( + "Whats the current weather in san francisco?" + ); + expect(result.tool_calls?.length).toBeGreaterThanOrEqual(1); +}); From c8688a252c986da397f58be770ba0c5590f980ba Mon Sep 17 00:00:00 2001 From: Brace Sproul Date: Fri, 6 Sep 2024 17:00:05 -0700 Subject: [PATCH 146/344] anthropic[patch]: Release 0.2.17 (#6713) --- libs/langchain-anthropic/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libs/langchain-anthropic/package.json b/libs/langchain-anthropic/package.json index 10e91c070907..2bce3129b107 100644 --- a/libs/langchain-anthropic/package.json +++ b/libs/langchain-anthropic/package.json @@ -1,6 +1,6 @@ { "name": "@langchain/anthropic", - "version": "0.2.16", + "version": "0.2.17", "description": "Anthropic integrations for LangChain.js", "type": "module", "engines": { From 3446b9c4e9c1c135aba56f3b63ad2ab0c271d391 Mon Sep 17 00:00:00 2001 From: Tommy Date: Sat, 7 Sep 2024 09:48:49 +0900 Subject: [PATCH 147/344] docs[patch]: fix chroma distance function reference link (#6714) --- examples/src/indexes/vector_stores/chroma/fromDocs.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/src/indexes/vector_stores/chroma/fromDocs.ts b/examples/src/indexes/vector_stores/chroma/fromDocs.ts index d8f99030c45b..472f2dcdad8f 100644 --- a/examples/src/indexes/vector_stores/chroma/fromDocs.ts +++ b/examples/src/indexes/vector_stores/chroma/fromDocs.ts @@ -12,7 +12,7 @@ const vectorStore = await Chroma.fromDocuments(docs, new OpenAIEmbeddings(), { url: "http://localhost:8000", // Optional, will default to this value collectionMetadata: { "hnsw:space": "cosine", - }, // Optional, can be used to specify the distance method of the embedding space https://docs.trychroma.com/usage-guide#changing-the-distance-function + }, // Optional, can be used to specify the distance method of the embedding space https://docs.trychroma.com/guides#changing-the-distance-function }); // Search for the most similar document From 2b6872b649a1b3c08c1d8ac337236a43be0614c4 Mon Sep 17 00:00:00 2001 From: Jacob Lee Date: Tue, 10 Sep 2024 11:17:29 -0700 Subject: [PATCH 148/344] fix(core): Pin TypeScript version for now for export test failures, add asyncDispose (#6723) --- environment_tests/test-exports-tsc/main.ts | 4 ++-- environment_tests/test-exports-tsc/package.json | 5 +---- langchain-core/src/utils/stream.ts | 14 +++++++++++++- 3 files changed, 16 insertions(+), 7 deletions(-) diff --git a/environment_tests/test-exports-tsc/main.ts b/environment_tests/test-exports-tsc/main.ts index 861bbf86c2db..2303896a168f 100644 --- a/environment_tests/test-exports-tsc/main.ts +++ b/environment_tests/test-exports-tsc/main.ts @@ -1,5 +1,5 @@ import { ChatOpenAI } from "@langchain/openai"; -import { createOpenAIFunctionsAgent, AgentExecutor } from "langchain/agents"; +import { createOpenAIToolsAgent, AgentExecutor } from "langchain/agents"; import { pull } from "langchain/hub"; import type { ChatPromptTemplate } from "@langchain/core/prompts"; @@ -11,7 +11,7 @@ const prompt = await pull( "hwchase17/openai-functions-agent" ); -const agent = await createOpenAIFunctionsAgent({ +const agent = await createOpenAIToolsAgent({ llm: model, prompt, tools: [] diff --git a/environment_tests/test-exports-tsc/package.json b/environment_tests/test-exports-tsc/package.json index 812e01a2516c..0facd117bf18 100644 --- a/environment_tests/test-exports-tsc/package.json +++ b/environment_tests/test-exports-tsc/package.json @@ -20,13 +20,10 @@ "@langchain/core": "workspace:*", "@langchain/openai": "workspace:*", "langchain": "workspace:*", - "typescript": "latest" + "typescript": "5.5.4" }, "devDependencies": { "@types/node": "^18.15.11", "prettier": "^2.8.3" - }, - "resolutions": { - "@langchain/core": "~0.2.0" } } diff --git a/langchain-core/src/utils/stream.ts b/langchain-core/src/utils/stream.ts index 91a9810e2d25..aa9db0604637 100644 --- a/langchain-core/src/utils/stream.ts +++ b/langchain-core/src/utils/stream.ts @@ -71,6 +71,12 @@ export class IterableReadableStream return this; } + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore Not present in Node 18 types, required in latest Node 22 + async [Symbol.asyncDispose]() { + await this.return(); + } + static fromReadableStream(stream: ReadableStream) { // From https://developer.mozilla.org/en-US/docs/Web/API/Streams_API/Using_readable_streams#reading_the_stream const reader = stream.getReader(); @@ -245,7 +251,7 @@ export class AsyncGeneratorWithSetup< } async return( - value: TReturn | PromiseLike + value?: TReturn | PromiseLike ): Promise> { return this.generator.return(value); } @@ -257,6 +263,12 @@ export class AsyncGeneratorWithSetup< [Symbol.asyncIterator]() { return this; } + + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore Not present in Node 18 types, required in latest Node 22 + async [Symbol.asyncDispose]() { + await this.return(); + } } export async function pipeGeneratorWithSetup< From d89745cf0f3e21fd41f2fcfbddf59abd7b95734e Mon Sep 17 00:00:00 2001 From: Jacob Lee Date: Tue, 10 Sep 2024 11:33:09 -0700 Subject: [PATCH 149/344] feat(core,openai): Adds streaming support for OpenAI withStructuredOutput (#6721) --- langchain-core/src/messages/ai.ts | 4 + .../openai_tools/json_output_tools_parsers.ts | 122 +++++++++++---- .../tests/json_output_tools_parser.test.ts | 145 +++++++++++++++++- .../src/output_parsers/transform.ts | 4 + .../chat_models_structured_output.int.test.ts | 86 +++++++---- 5 files changed, 304 insertions(+), 57 deletions(-) diff --git a/langchain-core/src/messages/ai.ts b/langchain-core/src/messages/ai.ts index 9c542af4e28b..c8a5311f040c 100644 --- a/langchain-core/src/messages/ai.ts +++ b/langchain-core/src/messages/ai.ts @@ -143,6 +143,10 @@ export function isAIMessage(x: BaseMessage): x is AIMessage { return x._getType() === "ai"; } +export function isAIMessageChunk(x: BaseMessageChunk): x is AIMessageChunk { + return x._getType() === "ai"; +} + export type AIMessageChunkFields = AIMessageFields & { tool_call_chunks?: ToolCallChunk[]; }; diff --git a/langchain-core/src/output_parsers/openai_tools/json_output_tools_parsers.ts b/langchain-core/src/output_parsers/openai_tools/json_output_tools_parsers.ts index fad6f82206a5..fe7987617d60 100644 --- a/langchain-core/src/output_parsers/openai_tools/json_output_tools_parsers.ts +++ b/langchain-core/src/output_parsers/openai_tools/json_output_tools_parsers.ts @@ -1,8 +1,13 @@ import { z } from "zod"; -import { ChatGeneration } from "../../outputs.js"; -import { BaseLLMOutputParser, OutputParserException } from "../base.js"; +import { ChatGeneration, ChatGenerationChunk } from "../../outputs.js"; +import { OutputParserException } from "../base.js"; import { parsePartialJson } from "../json.js"; import { InvalidToolCall, ToolCall } from "../../messages/tool.js"; +import { + BaseCumulativeTransformOutputParser, + BaseCumulativeTransformOutputParserInput, +} from "../transform.js"; +import { isAIMessage } from "../../messages/ai.js"; export type ParsedToolCall = { id?: string; @@ -23,7 +28,7 @@ export type ParsedToolCall = { export type JsonOutputToolsParserParams = { /** Whether to return the tool call id. */ returnId?: boolean; -}; +} & BaseCumulativeTransformOutputParserInput; export function parseToolCall( // eslint-disable-next-line @typescript-eslint/no-explicit-any @@ -35,6 +40,11 @@ export function parseToolCall( rawToolCall: Record, options?: { returnId?: boolean; partial?: false } ): ToolCall; +export function parseToolCall( + // eslint-disable-next-line @typescript-eslint/no-explicit-any + rawToolCall: Record, + options?: { returnId?: boolean; partial?: boolean } +): ToolCall | undefined; export function parseToolCall( // eslint-disable-next-line @typescript-eslint/no-explicit-any rawToolCall: Record, @@ -112,9 +122,9 @@ export function makeInvalidToolCall( /** * Class for parsing the output of a tool-calling LLM into a JSON object. */ -export class JsonOutputToolsParser extends BaseLLMOutputParser< - ParsedToolCall[] -> { +export class JsonOutputToolsParser< + T +> extends BaseCumulativeTransformOutputParser { static lc_name() { return "JsonOutputToolsParser"; } @@ -130,31 +140,64 @@ export class JsonOutputToolsParser extends BaseLLMOutputParser< this.returnId = fields?.returnId ?? this.returnId; } + protected _diff() { + throw new Error("Not supported."); + } + + async parse(): Promise { + throw new Error("Not implemented."); + } + + async parseResult(generations: ChatGeneration[]): Promise { + const result = await this.parsePartialResult(generations, false); + return result; + } + /** * Parses the output and returns a JSON object. If `argsOnly` is true, * only the arguments of the function call are returned. * @param generations The output of the LLM to parse. * @returns A JSON object representation of the function call or its arguments. */ - async parseResult(generations: ChatGeneration[]): Promise { - const toolCalls = generations[0].message.additional_kwargs.tool_calls; - if (!toolCalls) { - throw new Error( - `No tools_call in message ${JSON.stringify(generations)}` + async parsePartialResult( + generations: ChatGenerationChunk[] | ChatGeneration[], + partial = true + // eslint-disable-next-line @typescript-eslint/no-explicit-any + ): Promise { + const message = generations[0].message; + let toolCalls; + if (isAIMessage(message) && message.tool_calls?.length) { + toolCalls = message.tool_calls.map((toolCall) => { + const { id, ...rest } = toolCall; + if (!this.returnId) { + return rest; + } + return { + id, + ...rest, + }; + }); + } else if (message.additional_kwargs.tool_calls !== undefined) { + const rawToolCalls = JSON.parse( + JSON.stringify(message.additional_kwargs.tool_calls) ); + toolCalls = rawToolCalls.map((rawToolCall: Record) => { + return parseToolCall(rawToolCall, { returnId: this.returnId, partial }); + }); + } + if (!toolCalls) { + return []; } - const clonedToolCalls = JSON.parse(JSON.stringify(toolCalls)); const parsedToolCalls = []; - for (const toolCall of clonedToolCalls) { - const parsedToolCall = parseToolCall(toolCall, { partial: true }); - if (parsedToolCall !== undefined) { + for (const toolCall of toolCalls) { + if (toolCall !== undefined) { // backward-compatibility with previous // versions of Langchain JS, which uses `name` and `arguments` // @ts-expect-error name and arguemnts are defined by Object.defineProperty const backwardsCompatibleToolCall: ParsedToolCall = { - type: parsedToolCall.name, - args: parsedToolCall.args, - id: parsedToolCall.id, + type: toolCall.name, + args: toolCall.args, + id: toolCall.id, }; Object.defineProperty(backwardsCompatibleToolCall, "name", { get() { @@ -180,10 +223,8 @@ export type JsonOutputKeyToolsParserParams< > = { keyName: string; returnSingle?: boolean; - /** Whether to return the tool call id. */ - returnId?: boolean; zodSchema?: z.ZodType; -}; +} & JsonOutputToolsParserParams; /** * Class for parsing the output of a tool-calling LLM into a JSON object if you are @@ -192,7 +233,7 @@ export type JsonOutputKeyToolsParserParams< export class JsonOutputKeyToolsParser< // eslint-disable-next-line @typescript-eslint/no-explicit-any T extends Record = Record -> extends BaseLLMOutputParser { +> extends JsonOutputToolsParser { static lc_name() { return "JsonOutputKeyToolsParser"; } @@ -209,15 +250,12 @@ export class JsonOutputKeyToolsParser< /** Whether to return only the first tool call. */ returnSingle = false; - initialParser: JsonOutputToolsParser; - zodSchema?: z.ZodType; constructor(params: JsonOutputKeyToolsParserParams) { super(params); this.keyName = params.keyName; this.returnSingle = params.returnSingle ?? this.returnSingle; - this.initialParser = new JsonOutputToolsParser(params); this.zodSchema = params.zodSchema; } @@ -240,17 +278,45 @@ export class JsonOutputKeyToolsParser< } } + // eslint-disable-next-line @typescript-eslint/no-explicit-any + async parsePartialResult(generations: ChatGeneration[]): Promise { + const results = await super.parsePartialResult(generations); + const matchingResults = results.filter( + (result: ParsedToolCall) => result.type === this.keyName + ); + // eslint-disable-next-line @typescript-eslint/no-explicit-any + let returnedValues: ParsedToolCall[] | Record[] = + matchingResults; + if (!matchingResults.length) { + return undefined; + } + if (!this.returnId) { + returnedValues = matchingResults.map( + (result: ParsedToolCall) => result.args + ); + } + if (this.returnSingle) { + return returnedValues[0]; + } + return returnedValues; + } + // eslint-disable-next-line @typescript-eslint/no-explicit-any async parseResult(generations: ChatGeneration[]): Promise { - const results = await this.initialParser.parseResult(generations); + const results = await super.parsePartialResult(generations, false); const matchingResults = results.filter( - (result) => result.type === this.keyName + (result: ParsedToolCall) => result.type === this.keyName ); // eslint-disable-next-line @typescript-eslint/no-explicit-any let returnedValues: ParsedToolCall[] | Record[] = matchingResults; + if (!matchingResults.length) { + return undefined; + } if (!this.returnId) { - returnedValues = matchingResults.map((result) => result.args); + returnedValues = matchingResults.map( + (result: ParsedToolCall) => result.args + ); } if (this.returnSingle) { return this._validateResult(returnedValues[0]); diff --git a/langchain-core/src/output_parsers/openai_tools/tests/json_output_tools_parser.test.ts b/langchain-core/src/output_parsers/openai_tools/tests/json_output_tools_parser.test.ts index bffe7e0249af..131a4d59dd39 100644 --- a/langchain-core/src/output_parsers/openai_tools/tests/json_output_tools_parser.test.ts +++ b/langchain-core/src/output_parsers/openai_tools/tests/json_output_tools_parser.test.ts @@ -1,8 +1,10 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ import { test, expect } from "@jest/globals"; import { z } from "zod"; import { JsonOutputKeyToolsParser } from "../json_output_tools_parsers.js"; -import { AIMessage } from "../../../messages/index.js"; import { OutputParserException } from "../../base.js"; +import { AIMessage, AIMessageChunk } from "../../../messages/ai.js"; +import { RunnableLambda } from "../../../runnables/base.js"; test("JSONOutputKeyToolsParser invoke", async () => { const outputParser = new JsonOutputKeyToolsParser({ @@ -87,3 +89,144 @@ test("JSONOutputKeyToolsParser can validate a proper input", async () => { ); expect(result).toEqual({ testKey: "testval" }); }); + +test("JSONOutputKeyToolsParser invoke with a top-level tool call", async () => { + const outputParser = new JsonOutputKeyToolsParser({ + keyName: "testing", + returnSingle: true, + }); + const result = await outputParser.invoke( + new AIMessage({ + content: "", + tool_calls: [ + { + id: "test", + name: "testing", + args: { testKey: 9 }, + }, + ], + }) + ); + expect(result).toEqual({ testKey: 9 }); +}); + +test("JSONOutputKeyToolsParser with a top-level tool call and passed schema throws", async () => { + const outputParser = new JsonOutputKeyToolsParser({ + keyName: "testing", + returnSingle: true, + zodSchema: z.object({ + testKey: z.string(), + }), + }); + try { + await outputParser.invoke( + new AIMessage({ + content: "", + tool_calls: [ + { + id: "test", + name: "testing", + args: { testKey: 9 }, + }, + ], + }) + ); + } catch (e) { + expect(e).toBeInstanceOf(OutputParserException); + } +}); + +test("JSONOutputKeyToolsParser with a top-level tool call can validate a proper input", async () => { + const outputParser = new JsonOutputKeyToolsParser({ + keyName: "testing", + returnSingle: true, + zodSchema: z.object({ + testKey: z.string(), + }), + }); + const result = await outputParser.invoke( + new AIMessage({ + content: "", + tool_calls: [ + { + id: "test", + name: "testing", + args: { testKey: "testval" }, + }, + ], + }) + ); + expect(result).toEqual({ testKey: "testval" }); +}); + +test("JSONOutputKeyToolsParser can handle streaming input", async () => { + const outputParser = new JsonOutputKeyToolsParser({ + keyName: "testing", + returnSingle: true, + zodSchema: z.object({ + testKey: z.string(), + }), + }); + const fakeModel = RunnableLambda.from(async function* () { + yield new AIMessageChunk({ + content: "", + tool_call_chunks: [ + { + index: 0, + id: "test", + name: "testing", + args: `{ "testKey":`, + type: "tool_call_chunk", + }, + ], + }); + yield new AIMessageChunk({ + content: "", + tool_call_chunks: [], + }); + yield new AIMessageChunk({ + content: "", + tool_call_chunks: [ + { + index: 0, + id: "test", + args: ` "testv`, + type: "tool_call_chunk", + }, + ], + }); + yield new AIMessageChunk({ + content: "", + tool_call_chunks: [ + { + index: 0, + id: "test", + args: `al" }`, + type: "tool_call_chunk", + }, + ], + }); + }); + const stream = await (fakeModel as any).pipe(outputParser).stream(); + const chunks = []; + for await (const chunk of stream) { + chunks.push(chunk); + } + expect(chunks.length).toBeGreaterThan(1); + expect(chunks.at(-1)).toEqual({ testKey: "testval" }); + // TODO: Fix typing issue + const result = await (fakeModel as any).pipe(outputParser).invoke( + new AIMessage({ + content: "", + tool_calls: [ + { + id: "test", + name: "testing", + args: { testKey: "testval" }, + type: "tool_call", + }, + ], + }) + ); + expect(result).toEqual({ testKey: "testval" }); +}); diff --git a/langchain-core/src/output_parsers/transform.ts b/langchain-core/src/output_parsers/transform.ts index 64647e10feb1..384c3285f74c 100644 --- a/langchain-core/src/output_parsers/transform.ts +++ b/langchain-core/src/output_parsers/transform.ts @@ -134,4 +134,8 @@ export abstract class BaseCumulativeTransformOutputParser< } } } + + getFormatInstructions(): string { + return ""; + } } diff --git a/libs/langchain-openai/src/tests/chat_models_structured_output.int.test.ts b/libs/langchain-openai/src/tests/chat_models_structured_output.int.test.ts index c6a6c97dbeda..bb3057dbe7d0 100644 --- a/libs/langchain-openai/src/tests/chat_models_structured_output.int.test.ts +++ b/libs/langchain-openai/src/tests/chat_models_structured_output.int.test.ts @@ -25,10 +25,8 @@ test("withStructuredOutput zod schema function calling", async () => { ); const prompt = ChatPromptTemplate.fromMessages([ - "system", - "You are VERY bad at math and must always use a calculator.", - "human", - "Please help me!! What is 2 + 2?", + ["system", "You are VERY bad at math and must always use a calculator."], + ["human", "Please help me!! What is 2 + 2?"], ]); const chain = prompt.pipe(modelWithStructuredOutput); const result = await chain.invoke({}); @@ -38,6 +36,41 @@ test("withStructuredOutput zod schema function calling", async () => { expect("number2" in result).toBe(true); }); +test("withStructuredOutput zod schema streaming", async () => { + const model = new ChatOpenAI({ + temperature: 0, + modelName: "gpt-4-turbo-preview", + }); + + const calculatorSchema = z.object({ + operation: z.enum(["add", "subtract", "multiply", "divide"]), + number1: z.number(), + number2: z.number(), + }); + const modelWithStructuredOutput = model.withStructuredOutput( + calculatorSchema, + { + name: "calculator", + } + ); + + const prompt = ChatPromptTemplate.fromMessages([ + ["system", "You are VERY bad at math and must always use a calculator."], + ["human", "Please help me!! What is 2 + 2?"], + ]); + const chain = prompt.pipe(modelWithStructuredOutput); + const stream = await chain.stream({}); + const chunks = []; + for await (const chunk of stream) { + chunks.push(chunk); + } + expect(chunks.length).toBeGreaterThan(1); + const result = chunks.at(-1) ?? {}; + expect("operation" in result).toBe(true); + expect("number1" in result).toBe(true); + expect("number2" in result).toBe(true); +}); + test("withStructuredOutput zod schema JSON mode", async () => { const model = new ChatOpenAI({ temperature: 0, @@ -58,15 +91,16 @@ test("withStructuredOutput zod schema JSON mode", async () => { ); const prompt = ChatPromptTemplate.fromMessages([ - "system", - `You are VERY bad at math and must always use a calculator. + [ + "system", + `You are VERY bad at math and must always use a calculator. Respond with a JSON object containing three keys: 'operation': the type of operation to execute, either 'add', 'subtract', 'multiply' or 'divide', 'number1': the first number to operate on, 'number2': the second number to operate on. `, - "human", - "Please help me!! What is 2 + 2?", + ], + ["human", "Please help me!! What is 2 + 2?"], ]); const chain = prompt.pipe(modelWithStructuredOutput); const result = await chain.invoke({}); @@ -93,10 +127,8 @@ test("withStructuredOutput JSON schema function calling", async () => { }); const prompt = ChatPromptTemplate.fromMessages([ - "system", - `You are VERY bad at math and must always use a calculator.`, - "human", - "Please help me!! What is 2 + 2?", + ["system", `You are VERY bad at math and must always use a calculator.`], + ["human", "Please help me!! What is 2 + 2?"], ]); const chain = prompt.pipe(modelWithStructuredOutput); const result = await chain.invoke({}); @@ -123,10 +155,8 @@ test("withStructuredOutput OpenAI function definition function calling", async ( }); const prompt = ChatPromptTemplate.fromMessages([ - "system", - `You are VERY bad at math and must always use a calculator.`, - "human", - "Please help me!! What is 2 + 2?", + ["system", `You are VERY bad at math and must always use a calculator.`], + ["human", "Please help me!! What is 2 + 2?"], ]); const chain = prompt.pipe(modelWithStructuredOutput); const result = await chain.invoke({}); @@ -156,15 +186,16 @@ test("withStructuredOutput JSON schema JSON mode", async () => { ); const prompt = ChatPromptTemplate.fromMessages([ - "system", - `You are VERY bad at math and must always use a calculator. + [ + "system", + `You are VERY bad at math and must always use a calculator. Respond with a JSON object containing three keys: 'operation': the type of operation to execute, either 'add', 'subtract', 'multiply' or 'divide', 'number1': the first number to operate on, 'number2': the second number to operate on. `, - "human", - "Please help me!! What is 2 + 2?", + ], + ["human", "Please help me!! What is 2 + 2?"], ]); const chain = prompt.pipe(modelWithStructuredOutput); const result = await chain.invoke({}); @@ -196,15 +227,16 @@ test("withStructuredOutput JSON schema", async () => { const modelWithStructuredOutput = model.withStructuredOutput(jsonSchema); const prompt = ChatPromptTemplate.fromMessages([ - "system", - `You are VERY bad at math and must always use a calculator. + [ + "system", + `You are VERY bad at math and must always use a calculator. Respond with a JSON object containing three keys: 'operation': the type of operation to execute, either 'add', 'subtract', 'multiply' or 'divide', 'number1': the first number to operate on, 'number2': the second number to operate on. `, - "human", - "Please help me!! What is 2 + 2?", + ], + ["human", "Please help me!! What is 2 + 2?"], ]); const chain = prompt.pipe(modelWithStructuredOutput); const result = await chain.invoke({}); @@ -234,10 +266,8 @@ test("withStructuredOutput includeRaw true", async () => { ); const prompt = ChatPromptTemplate.fromMessages([ - "system", - "You are VERY bad at math and must always use a calculator.", - "human", - "Please help me!! What is 2 + 2?", + ["system", "You are VERY bad at math and must always use a calculator."], + ["human", "Please help me!! What is 2 + 2?"], ]); const chain = prompt.pipe(modelWithStructuredOutput); const result = await chain.invoke({}); From 24400db1535fb61ab6f2010869b3ff3f9b18be8b Mon Sep 17 00:00:00 2001 From: stanoswald <53464809+stanoswald@users.noreply.github.com> Date: Wed, 11 Sep 2024 06:14:26 +0800 Subject: [PATCH 150/344] baidu-qianfan[patch]: Fix streaming mode of Qianfan (#6661) --- .../src/chat_models.ts | 219 ++++++------------ 1 file changed, 76 insertions(+), 143 deletions(-) diff --git a/libs/langchain-baidu-qianfan/src/chat_models.ts b/libs/langchain-baidu-qianfan/src/chat_models.ts index 6fc98ff48ce5..5d665e121b21 100644 --- a/libs/langchain-baidu-qianfan/src/chat_models.ts +++ b/libs/langchain-baidu-qianfan/src/chat_models.ts @@ -15,7 +15,6 @@ import { } from "@langchain/core/outputs"; import { CallbackManagerForLLMRun } from "@langchain/core/callbacks/manager"; import { getEnvironmentVariable } from "@langchain/core/utils/env"; -import { convertEventStreamToIterableReadableDataStream } from "@langchain/core/utils/event_source_parse"; import { ChatCompletion } from "@baiducloud/qianfan"; /** @@ -60,8 +59,12 @@ interface ChatCompletionResponse { id: string; object: string; created: number; + sentence_id: number; + is_end: boolean; + is_truncated: boolean; result: string; need_clear_history: boolean; + finish_reason: string; usage: TokenUsage; } @@ -305,167 +308,96 @@ export class ChatBaiduQianfan private _ensureMessages(messages: BaseMessage[]): Qianfan[] { return messages.map((message) => ({ role: messageToQianfanRole(message), - content: message.text, + content: message.content.toString(), })); } /** @ignore */ async _generate( messages: BaseMessage[], - _options?: this["ParsedCallOptions"], + options: this["ParsedCallOptions"], runManager?: CallbackManagerForLLMRun ): Promise { - const tokenUsage: TokenUsage = {}; - - const params = this.invocationParams(); + if (this.streaming) { + let finalChunk: ChatGenerationChunk | undefined; + const stream = this._streamResponseChunks(messages, options, runManager); + for await (const chunk of stream) { + if (finalChunk === undefined) { + finalChunk = chunk; + } else { + finalChunk = finalChunk.concat(chunk); + } + } - // Qianfan requires the system message to be put in the params, not messages array - const systemMessage = messages.find( - (message) => message._getType() === "system" - ); - if (systemMessage) { - // eslint-disable-next-line no-param-reassign - messages = messages.filter((message) => message !== systemMessage); - params.system = systemMessage.text; - } - const messagesMapped = this._ensureMessages(messages); + if (finalChunk === undefined) { + throw new Error("No chunks returned from BaiduQianFan API."); + } - const data = params.stream - ? await new Promise((resolve, reject) => { - let rejected = false; - this.completionWithRetry( - { - ...params, - messages: messagesMapped, - }, - true, - (event) => { - resolve(event.data); - // eslint-disable-next-line no-void - void runManager?.handleLLMNewToken(event.data ?? ""); - } - ).catch((error) => { - if (!rejected) { - rejected = true; - reject(error); - } - }); - }) - : await this.completionWithRetry( + return { + generations: [ { - ...params, - messages: messagesMapped, + text: finalChunk.text, + message: finalChunk.message, }, - false - ).then((data) => { - if (data?.error_code) { - throw new Error(data?.error_msg); - } - return data; - }); - - const { - completion_tokens: completionTokens, - prompt_tokens: promptTokens, - total_tokens: totalTokens, - } = data.usage ?? {}; - - if (completionTokens) { - tokenUsage.completionTokens = - (tokenUsage.completionTokens ?? 0) + completionTokens; - } - - if (promptTokens) { - tokenUsage.promptTokens = (tokenUsage.promptTokens ?? 0) + promptTokens; - } - - if (totalTokens) { - tokenUsage.totalTokens = (tokenUsage.totalTokens ?? 0) + totalTokens; + ], + llmOutput: finalChunk.generationInfo?.usage ?? {}, + }; + } else { + const params = this.invocationParams(); + + const systemMessage = messages.find( + (message) => message._getType() === "system" + ); + if (systemMessage) { + // eslint-disable-next-line no-param-reassign + messages = messages.filter((message) => message !== systemMessage); + params.system = systemMessage.content.toString(); + } + const messagesMapped = this._ensureMessages(messages); + + const data = (await this.completionWithRetry( + { + ...params, + messages: messagesMapped, + }, + false + )) as ChatCompletionResponse; + + const tokenUsage = data.usage || {}; + + const generations: ChatGeneration[] = [ + { + text: data.result || "", + message: new AIMessage(data.result || ""), + }, + ]; + + return { + generations, + llmOutput: { tokenUsage }, + }; } - - const generations: ChatGeneration[] = []; - const text = data.result ?? ""; - generations.push({ - text, - message: new AIMessage(text), - }); - return { - generations, - llmOutput: { tokenUsage }, - }; } /** @ignore */ async completionWithRetry( request: ChatCompletionRequest, - stream: boolean, - onmessage?: (event: MessageEvent) => void - ) { + stream: boolean + ): Promise< + ChatCompletionResponse | AsyncIterableIterator + > { const makeCompletionRequest = async () => { - console.log(request); const response = await this.client.chat(request, this.model); - if (!stream) { return response; } else { - let streamResponse = { result: "" } as { - id: string; - object: string; - created: number; - sentence_id?: number; - result: string; - need_clear_history: boolean; - usage: TokenUsage; - }; - // eslint-disable-next-line @typescript-eslint/no-explicit-any - for await (const message of response as AsyncIterableIterator) { - // 返回结果 - if (!streamResponse) { - streamResponse = { - id: message.id, - object: message.object, - created: message.created, - result: message.result, - need_clear_history: message.need_clear_history, - usage: message.usage, - }; - } else { - streamResponse.result += message.result; - streamResponse.created = message.created; - streamResponse.need_clear_history = message.need_clear_history; - streamResponse.usage = message.usage; - } - } - const event = new MessageEvent("message", { - data: streamResponse, - }); - onmessage?.(event); + return response as AsyncIterableIterator; } }; return this.caller.call(makeCompletionRequest); } - private async createStream(request: ChatCompletionRequest) { - const response = await this.client.chat( - { - ...request, - stream: true, - }, - this.model - ); - - return convertEventStreamToIterableReadableDataStream(response); - } - - private _deserialize(json: string) { - try { - return JSON.parse(json); - } catch (e) { - console.warn(`Received a non-JSON parseable chunk: ${json}`); - } - } - async *_streamResponseChunks( messages: BaseMessage[], _options?: this["ParsedCallOptions"], @@ -476,27 +408,28 @@ export class ChatBaiduQianfan stream: true, }; - // Qianfan requires the system message to be put in the params, not messages array const systemMessage = messages.find( (message) => message._getType() === "system" ); if (systemMessage) { // eslint-disable-next-line no-param-reassign messages = messages.filter((message) => message !== systemMessage); - parameters.system = systemMessage.text; + parameters.system = systemMessage.content.toString(); } const messagesMapped = this._ensureMessages(messages); - const stream = await this.caller.call(async () => - this.createStream({ - ...parameters, - messages: messagesMapped, - }) - ); + const stream = (await this.caller.call(async () => + this.completionWithRetry( + { + ...parameters, + messages: messagesMapped, + }, + true + ) + )) as AsyncIterableIterator; for await (const chunk of stream) { - const deserializedChunk = this._deserialize(chunk); - const { result, is_end, id } = deserializedChunk; + const { result, is_end, id } = chunk; yield new ChatGenerationChunk({ text: result, message: new AIMessageChunk({ content: result }), From 47f846dff564ebd678ee12a580e09b4663219e5c Mon Sep 17 00:00:00 2001 From: rxliuli Date: Wed, 11 Sep 2024 06:27:46 +0800 Subject: [PATCH 151/344] fix(anthropic): Do not check for apiKey existence when createClient parameters are present (#6716) Co-authored-by: Jacob Lee --- docs/core_docs/docs/integrations/chat/anthropic.ipynb | 2 +- libs/langchain-anthropic/src/chat_models.ts | 5 +---- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/docs/core_docs/docs/integrations/chat/anthropic.ipynb b/docs/core_docs/docs/integrations/chat/anthropic.ipynb index 4786d1f68a9b..9fafc787406e 100644 --- a/docs/core_docs/docs/integrations/chat/anthropic.ipynb +++ b/docs/core_docs/docs/integrations/chat/anthropic.ipynb @@ -953,7 +953,7 @@ "const customClient = new AnthropicVertex();\n", "\n", "const modelWithCustomClient = new ChatAnthropic({\n", - " modelName: \"claude-3-sonnet-20240229\",\n", + " modelName: \"claude-3-sonnet@20240229\",\n", " maxRetries: 0,\n", " createClient: () => customClient,\n", "});\n", diff --git a/libs/langchain-anthropic/src/chat_models.ts b/libs/langchain-anthropic/src/chat_models.ts index c8edf3c17d0d..1e8853ff5b8a 100644 --- a/libs/langchain-anthropic/src/chat_models.ts +++ b/libs/langchain-anthropic/src/chat_models.ts @@ -633,7 +633,7 @@ export class ChatAnthropicMessages< fields?.anthropicApiKey ?? getEnvironmentVariable("ANTHROPIC_API_KEY"); - if (!this.anthropicApiKey) { + if (!this.anthropicApiKey && !fields?.createClient) { throw new Error("Anthropic API key not found"); } this.clientOptions = fields?.clientOptions ?? {}; @@ -949,9 +949,6 @@ export class ChatAnthropicMessages< ): Promise { if (!this.batchClient) { const options = this.apiUrl ? { baseURL: this.apiUrl } : undefined; - if (!this.apiKey) { - throw new Error("Missing Anthropic API key."); - } this.batchClient = this.createClient({ ...this.clientOptions, ...options, From 964e8bd4085d7d3f6fc6c582866317bc4b26301b Mon Sep 17 00:00:00 2001 From: Jacob Lee Date: Tue, 10 Sep 2024 15:32:29 -0700 Subject: [PATCH 152/344] feat(community): Add arcjet integration (#6725) Co-authored-by: e-moran Co-authored-by: Eoin Power-Moran <37004949+e-moran@users.noreply.github.com> --- docs/core_docs/.gitignore | 6 +- .../docs/integrations/chat/arcjet.ipynb | 164 +++++++++++ .../docs/integrations/llms/arcjet.ipynb | 156 +++++++++++ docs/core_docs/src/theme/FeatureTables.js | 15 + libs/langchain-community/.gitignore | 8 + libs/langchain-community/langchain.config.js | 4 + libs/langchain-community/package.json | 31 +++ .../src/chat_models/arcjet.ts | 194 +++++++++++++ .../src/chat_models/tests/chatarcjet.test.ts | 263 ++++++++++++++++++ libs/langchain-community/src/llms/arcjet.ts | 80 ++++++ .../src/llms/tests/arcjet.test.ts | 181 ++++++++++++ .../src/load/import_constants.ts | 2 + yarn.lock | 20 ++ 13 files changed, 1123 insertions(+), 1 deletion(-) create mode 100644 docs/core_docs/docs/integrations/chat/arcjet.ipynb create mode 100644 docs/core_docs/docs/integrations/llms/arcjet.ipynb create mode 100644 libs/langchain-community/src/chat_models/arcjet.ts create mode 100644 libs/langchain-community/src/chat_models/tests/chatarcjet.test.ts create mode 100644 libs/langchain-community/src/llms/arcjet.ts create mode 100644 libs/langchain-community/src/llms/tests/arcjet.test.ts diff --git a/docs/core_docs/.gitignore b/docs/core_docs/.gitignore index ca1dd4efb878..305859f43ac6 100644 --- a/docs/core_docs/.gitignore +++ b/docs/core_docs/.gitignore @@ -304,6 +304,8 @@ docs/integrations/llms/cloudflare_workersai.md docs/integrations/llms/cloudflare_workersai.mdx docs/integrations/llms/bedrock.md docs/integrations/llms/bedrock.mdx +docs/integrations/llms/arcjet.md +docs/integrations/llms/arcjet.mdx docs/integrations/llms/azure.md docs/integrations/llms/azure.mdx docs/integrations/chat/togetherai.md @@ -330,6 +332,8 @@ docs/integrations/chat/bedrock_converse.md docs/integrations/chat/bedrock_converse.mdx docs/integrations/chat/bedrock.md docs/integrations/chat/bedrock.mdx +docs/integrations/chat/arcjet.md +docs/integrations/chat/arcjet.mdx docs/integrations/chat/azure.md docs/integrations/chat/azure.mdx docs/integrations/chat/anthropic.md @@ -371,4 +375,4 @@ docs/integrations/document_loaders/web_loaders/pdf.mdx docs/integrations/document_loaders/web_loaders/langsmith.md docs/integrations/document_loaders/web_loaders/langsmith.mdx docs/integrations/document_loaders/web_loaders/firecrawl.md -docs/integrations/document_loaders/web_loaders/firecrawl.mdx \ No newline at end of file +docs/integrations/document_loaders/web_loaders/firecrawl.mdx diff --git a/docs/core_docs/docs/integrations/chat/arcjet.ipynb b/docs/core_docs/docs/integrations/chat/arcjet.ipynb new file mode 100644 index 000000000000..fcc83a6fb08f --- /dev/null +++ b/docs/core_docs/docs/integrations/chat/arcjet.ipynb @@ -0,0 +1,164 @@ +{ + "cells": [ + { + "cell_type": "raw", + "id": "67db2992", + "metadata": { + "vscode": { + "languageId": "raw" + } + }, + "source": [ + "---\n", + "sidebar_label: Arcjet Redact\n", + "---" + ] + }, + { + "cell_type": "markdown", + "id": "9597802c", + "metadata": {}, + "source": [ + "# Arcjet Redact\n", + "\n", + "The [Arcjet](https://arcjet.com) redact integration allows you to redact sensitive user information from your prompts before sending it to a Chat Model.\n", + "\n", + "Arcjet Redact runs entirely on your own machine and never sends data anywhere else, ensuring best in class privacy and performance.\n", + "\n", + "The Arcjet Redact object is not a chat model itself, instead it wraps an LLM. It redacts the text that is inputted to it and then unredacts the output of the wrapped chat model before returning it. \n", + "\n", + "\n", + "\n", + "## Overview\n", + "### Integration details\n", + "\n", + "| Class | Package | Local | Serializable | PY Support | Package downloads | Package latest |\n", + "| :--- | :--- | :---: | :---: | :---: | :---: | :---: |\n", + "| Arcjet | @langchain/community | ❌ | ✅ | ❌ | ![NPM - Downloads](https://img.shields.io/npm/dm/@langchain/community?style=flat-square&label=%20&) | ![NPM - Version](https://img.shields.io/npm/v/@langchain/community?style=flat-square&label=%20&) |\n", + "\n", + "### Installation\n", + "\n", + "Install the Arcjet Redaction Library:\n", + "\n", + "```{=mdx}\n", + "import IntegrationInstallTooltip from \"@mdx_components/integration_install_tooltip.mdx\";\n", + "import Npm2Yarn from \"@theme/Npm2Yarn\";\n", + "\n", + "\n", + "\n", + "\n", + " @arcjet/redact\n", + "\n", + "\n", + "```\n", + "\n", + "And install LangChain Community:\n", + "\n", + "\n", + "```{=mdx}\n", + "\n", + "\n", + "\n", + " @langchain/community\n", + "\n", + "\n", + "And now you're ready to start protecting your chat model calls with Arcjet Redaction!\n", + "\n", + "```" + ] + }, + { + "cell_type": "markdown", + "id": "0a760037", + "metadata": {}, + "source": [ + "## Usage" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "a0562a13", + "metadata": {}, + "outputs": [], + "source": [ + "import {\n", + " ArcjetRedact,\n", + " ArcjetSensitiveInfoType,\n", + "} from \"@langchain/community/chat_models/arcjet\";\n", + "import { ChatOpenAI } from \"@langchain/openai\";\n", + "\n", + "// Create an instance of another chat model for Arcjet to wrap\n", + "const openai = new ChatOpenAI({\n", + " temperature: 0.8,\n", + " model: \"gpt-3.5-turbo-0125\",\n", + "});\n", + "\n", + "const arcjetRedactOptions = {\n", + " // Specify a LLM that Arcjet Redact will call once it has redacted the input.\n", + " chatModel: openai,\n", + "\n", + " // Specify the list of entities that should be redacted.\n", + " // If this isn't specified then all entities will be redacted.\n", + " entities: [\"email\", \"phone-number\", \"ip-address\", \"custom-entity\"] as ArcjetSensitiveInfoType[],\n", + "\n", + " // You can provide a custom detect function to detect entities that we don't support yet.\n", + " // It takes a list of tokens and you return a list of identified types or undefined.\n", + " // The undefined types that you return should be added to the entities list if used.\n", + " detect: (tokens: string[]) => {\n", + " return tokens.map((t) => t === \"some-sensitive-info\" ? \"custom-entity\" : undefined)\n", + " },\n", + "\n", + " // The number of tokens to provide to the custom detect function. This defaults to 1.\n", + " // It can be used to provide additional context when detecting custom entity types.\n", + " contextWindowSize: 1,\n", + "\n", + " // This allows you to provide custom replacements when redacting. Please ensure\n", + " // that the replacements are unique so that unredaction works as expected.\n", + " replace: (identifiedType: string) => {\n", + " return identifiedType === \"email\" ? \"redacted@example.com\" : undefined;\n", + " },\n", + "};\n", + "\n", + "const arcjetRedact = new ArcjetRedact(arcjetRedactOptions);\n", + "\n", + "const response = await arcjetRedact.invoke(\n", + " \"My email address is test@example.com, here is some-sensitive-info\"\n", + ");" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "165c0f4f", + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "TypeScript", + "language": "typescript", + "name": "tslab" + }, + "language_info": { + "codemirror_mode": { + "mode": "typescript", + "name": "javascript", + "typescript": true + }, + "file_extension": ".ts", + "mimetype": "text/typescript", + "name": "typescript", + "version": "3.7.2" + }, + "vscode": { + "interpreter": { + "hash": "e971737741ff4ec9aff7dc6155a1060a59a8a6d52c757dbbe66bf8ee389494b1" + } + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/docs/core_docs/docs/integrations/llms/arcjet.ipynb b/docs/core_docs/docs/integrations/llms/arcjet.ipynb new file mode 100644 index 000000000000..730ed86c74a8 --- /dev/null +++ b/docs/core_docs/docs/integrations/llms/arcjet.ipynb @@ -0,0 +1,156 @@ +{ + "cells": [ + { + "cell_type": "raw", + "id": "67db2992", + "metadata": { + "vscode": { + "languageId": "raw" + } + }, + "source": [ + "---\n", + "sidebar_label: Arcjet Redact\n", + "---" + ] + }, + { + "cell_type": "markdown", + "id": "9597802c", + "metadata": {}, + "source": [ + "# Arcjet Redact\n", + "\n", + "The [Arcjet](https://arcjet.com) redact integration allows you to redact sensitive user information from your prompts before sending it to an LLM.\n", + "\n", + "Arcjet Redact runs entirely on your own machine and never sends data anywhere else, ensuring best in class privacy and performance.\n", + "\n", + "The Arcjet Redact object is not an LLM itself, instead it wraps an LLM. It redacts the text that is inputted to it and then unredacts the output of the wrapped LLM before returning it. \n", + "\n", + "\n", + "\n", + "## Overview\n", + "### Integration details\n", + "\n", + "| Class | Package | Local | Serializable | PY Support | Package downloads | Package latest |\n", + "| :--- | :--- | :---: | :---: | :---: | :---: | :---: |\n", + "| Arcjet | @langchain/community | ❌ | ✅ | ❌ | ![NPM - Downloads](https://img.shields.io/npm/dm/@langchain/community?style=flat-square&label=%20&) | ![NPM - Version](https://img.shields.io/npm/v/@langchain/community?style=flat-square&label=%20&) |\n", + "\n", + "### Installation\n", + "\n", + "Install the Arcjet Redaction Library:\n", + "\n", + "```{=mdx}\n", + "import IntegrationInstallTooltip from \"@mdx_components/integration_install_tooltip.mdx\";\n", + "import Npm2Yarn from \"@theme/Npm2Yarn\";\n", + "\n", + "\n", + "\n", + "\n", + " @arcjet/redact\n", + "\n", + "\n", + "```\n", + "\n", + "And install LangChain Community:\n", + "\n", + "\n", + "```{=mdx}\n", + "\n", + "\n", + "\n", + " @langchain/community\n", + "\n", + "\n", + "And now you're ready to start protecting your LLM calls with Arcjet Redaction!\n", + "\n", + "```" + ] + }, + { + "cell_type": "markdown", + "id": "0a760037", + "metadata": {}, + "source": [ + "## Usage" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "a0562a13", + "metadata": {}, + "outputs": [], + "source": [ + "import {\n", + " ArcjetRedact,\n", + " ArcjetSensitiveInfoType,\n", + "} from \"@langchain/community/llms/arcjet\";\n", + "import { OpenAI } from \"@langchain/openai\";\n", + "\n", + "// Create an instance of another LLM for Arcjet to wrap\n", + "const openai = new OpenAI({\n", + " modelName: \"gpt-3.5-turbo-instruct\",\n", + " openAIApiKey: process.env.OPENAI_API_KEY,\n", + "});\n", + "\n", + "const arcjetRedactOptions = {\n", + " // Specify a LLM that Arcjet Redact will call once it has redacted the input.\n", + " llm: openai,\n", + "\n", + " // Specify the list of entities that should be redacted.\n", + " // If this isn't specified then all entities will be redacted.\n", + " entities: [\"email\", \"phone-number\", \"ip-address\", \"credit-card\"] as ArcjetSensitiveInfoType[],\n", + "\n", + " // You can provide a custom detect function to detect entities that we don't support yet.\n", + " // It takes a list of tokens and you return a list of identified types or undefined.\n", + " // The undefined types that you return should be added to the entities list if used.\n", + " detect: (tokens: string[]) => {\n", + " return tokens.map((t) => t === \"some-sensitive-info\" ? \"custom-entity\" : undefined)\n", + " },\n", + "\n", + " // The number of tokens to provide to the custom detect function. This defaults to 1.\n", + " // It can be used to provide additional context when detecting custom entity types.\n", + " contextWindowSize: 1,\n", + "\n", + " // This allows you to provide custom replacements when redacting. Please ensure\n", + " // that the replacements are unique so that unredaction works as expected.\n", + " replace: (identifiedType: string) => {\n", + " return identifiedType === \"email\" ? \"redacted@example.com\" : undefined;\n", + " },\n", + "\n", + "};\n", + "\n", + "const arcjetRedact = new ArcjetRedact(arcjetRedactOptions);\n", + "const response = await arcjetRedact.invoke(\n", + " \"My email address is test@example.com, here is some-sensitive-info\"\n", + ");" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "TypeScript", + "language": "typescript", + "name": "tslab" + }, + "language_info": { + "codemirror_mode": { + "mode": "typescript", + "name": "javascript", + "typescript": true + }, + "file_extension": ".ts", + "mimetype": "text/typescript", + "name": "typescript", + "version": "3.7.2" + }, + "vscode": { + "interpreter": { + "hash": "e971737741ff4ec9aff7dc6155a1060a59a8a6d52c757dbbe66bf8ee389494b1" + } + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/docs/core_docs/src/theme/FeatureTables.js b/docs/core_docs/src/theme/FeatureTables.js index 1870361af2b9..b2dec1539d53 100644 --- a/docs/core_docs/src/theme/FeatureTables.js +++ b/docs/core_docs/src/theme/FeatureTables.js @@ -239,6 +239,16 @@ const FEATURE_TABLES = { apiLink: "https://api.python.langchain.com/en/latest/chat_models/langchain_upstage.chat_models.ChatUpstage.html#langchain_upstage.chat_models.ChatUpstage", }, + { + name: "Arcjet Redact", + package: "langchain-community", + link: "arcjet", + structured_output: false, + tool_calling: false, + json_mode: false, + multimodal: false, + local: true, + }, ], }, llms: { @@ -261,6 +271,11 @@ const FEATURE_TABLES = { apiLink: "https://api.python.langchain.com/en/latest/llms/langchain_ai21.llms.AI21LLM.html#langchain_ai21.llms.AI21LLM", }, + { + name: "Arcjet Redact", + link: "arcjet", + package: "langchain-community", + }, { name: "AnthropicLLM", link: "anthropic", diff --git a/libs/langchain-community/.gitignore b/libs/langchain-community/.gitignore index 8f6f452a7468..299b955d097a 100644 --- a/libs/langchain-community/.gitignore +++ b/libs/langchain-community/.gitignore @@ -222,6 +222,10 @@ llms/aleph_alpha.cjs llms/aleph_alpha.js llms/aleph_alpha.d.ts llms/aleph_alpha.d.cts +llms/arcjet.cjs +llms/arcjet.js +llms/arcjet.d.ts +llms/arcjet.d.cts llms/bedrock.cjs llms/bedrock.js llms/bedrock.d.ts @@ -502,6 +506,10 @@ chat_models/alibaba_tongyi.cjs chat_models/alibaba_tongyi.js chat_models/alibaba_tongyi.d.ts chat_models/alibaba_tongyi.d.cts +chat_models/arcjet.cjs +chat_models/arcjet.js +chat_models/arcjet.d.ts +chat_models/arcjet.d.cts chat_models/baiduwenxin.cjs chat_models/baiduwenxin.js chat_models/baiduwenxin.d.ts diff --git a/libs/langchain-community/langchain.config.js b/libs/langchain-community/langchain.config.js index 3709f58812e7..5c9818185cec 100644 --- a/libs/langchain-community/langchain.config.js +++ b/libs/langchain-community/langchain.config.js @@ -91,6 +91,7 @@ export const config = { // llms "llms/ai21": "llms/ai21", "llms/aleph_alpha": "llms/aleph_alpha", + "llms/arcjet": "llms/arcjet", "llms/bedrock": "llms/bedrock/index", "llms/bedrock/web": "llms/bedrock/web", "llms/cloudflare_workersai": "llms/cloudflare_workersai", @@ -163,6 +164,7 @@ export const config = { "vectorstores/zep_cloud": "vectorstores/zep_cloud", // chat_models "chat_models/alibaba_tongyi": "chat_models/alibaba_tongyi", + "chat_models/arcjet": "chat_models/arcjet", "chat_models/baiduwenxin": "chat_models/baiduwenxin", "chat_models/bedrock": "chat_models/bedrock/index", "chat_models/bedrock/web": "chat_models/bedrock/web", @@ -351,6 +353,7 @@ export const config = { "embeddings/tencent_hunyuan/web", "embeddings/zhipuai", "llms/load", + "llms/arcjet", "llms/cohere", "llms/googlevertexai", "llms/googlevertexai/web", @@ -410,6 +413,7 @@ export const config = { "vectorstores/xata", "vectorstores/zep", "vectorstores/zep_cloud", + "chat_models/arcjet", "chat_models/bedrock", "chat_models/bedrock/web", "chat_models/googlevertexai", diff --git a/libs/langchain-community/package.json b/libs/langchain-community/package.json index 988f307255d0..85444a2936a0 100644 --- a/libs/langchain-community/package.json +++ b/libs/langchain-community/package.json @@ -48,6 +48,7 @@ "zod-to-json-schema": "^3.22.5" }, "devDependencies": { + "@arcjet/redact": "^v1.0.0-alpha.23", "@aws-crypto/sha256-js": "^5.0.0", "@aws-sdk/client-bedrock-agent-runtime": "^3.583.0", "@aws-sdk/client-bedrock-runtime": "^3.422.0", @@ -213,6 +214,7 @@ "youtubei.js": "^9.1.0" }, "peerDependencies": { + "@arcjet/redact": "^v1.0.0-alpha.23", "@aws-crypto/sha256-js": "^5.0.0", "@aws-sdk/client-bedrock-agent-runtime": "^3.583.0", "@aws-sdk/client-bedrock-runtime": "^3.422.0", @@ -335,6 +337,9 @@ "youtubei.js": "^9.1.0" }, "peerDependenciesMeta": { + "@arcjet/redact": { + "optional": true + }, "@aws-crypto/sha256-js": { "optional": true }, @@ -1204,6 +1209,15 @@ "import": "./llms/aleph_alpha.js", "require": "./llms/aleph_alpha.cjs" }, + "./llms/arcjet": { + "types": { + "import": "./llms/arcjet.d.ts", + "require": "./llms/arcjet.d.cts", + "default": "./llms/arcjet.d.ts" + }, + "import": "./llms/arcjet.js", + "require": "./llms/arcjet.cjs" + }, "./llms/bedrock": { "types": { "import": "./llms/bedrock.d.ts", @@ -1834,6 +1848,15 @@ "import": "./chat_models/alibaba_tongyi.js", "require": "./chat_models/alibaba_tongyi.cjs" }, + "./chat_models/arcjet": { + "types": { + "import": "./chat_models/arcjet.d.ts", + "require": "./chat_models/arcjet.d.cts", + "default": "./chat_models/arcjet.d.ts" + }, + "import": "./chat_models/arcjet.js", + "require": "./chat_models/arcjet.cjs" + }, "./chat_models/baiduwenxin": { "types": { "import": "./chat_models/baiduwenxin.d.ts", @@ -3286,6 +3309,10 @@ "llms/aleph_alpha.js", "llms/aleph_alpha.d.ts", "llms/aleph_alpha.d.cts", + "llms/arcjet.cjs", + "llms/arcjet.js", + "llms/arcjet.d.ts", + "llms/arcjet.d.cts", "llms/bedrock.cjs", "llms/bedrock.js", "llms/bedrock.d.ts", @@ -3566,6 +3593,10 @@ "chat_models/alibaba_tongyi.js", "chat_models/alibaba_tongyi.d.ts", "chat_models/alibaba_tongyi.d.cts", + "chat_models/arcjet.cjs", + "chat_models/arcjet.js", + "chat_models/arcjet.d.ts", + "chat_models/arcjet.d.cts", "chat_models/baiduwenxin.cjs", "chat_models/baiduwenxin.js", "chat_models/baiduwenxin.d.ts", diff --git a/libs/langchain-community/src/chat_models/arcjet.ts b/libs/langchain-community/src/chat_models/arcjet.ts new file mode 100644 index 000000000000..3933434fb856 --- /dev/null +++ b/libs/langchain-community/src/chat_models/arcjet.ts @@ -0,0 +1,194 @@ +/* eslint-disable no-param-reassign */ +/* eslint-disable no-plusplus */ +import { + BaseChatModel, + type BaseChatModelParams, +} from "@langchain/core/language_models/chat_models"; +import type { ArcjetSensitiveInfoType, RedactOptions } from "@arcjet/redact"; +import { CallbackManagerForLLMRun } from "@langchain/core/callbacks/manager"; +import { BaseMessage } from "@langchain/core/messages"; +import { ChatResult } from "@langchain/core/outputs"; + +type DetectSensitiveInfoEntities = ( + tokens: string[] +) => Array; +type ValidEntities = Array< + undefined extends Detect + ? ArcjetSensitiveInfoType + : Detect extends DetectSensitiveInfoEntities + ? ArcjetSensitiveInfoType | CustomEntities + : never +>; + +export interface ArcjetRedactOptions extends BaseChatModelParams { + chatModel: BaseChatModel; + entities?: ValidEntities; + contextWindowSize?: number; + detect?: Detect; + replace?: (entity: ValidEntities[number]) => string | undefined; +} + +export type { ArcjetSensitiveInfoType, RedactOptions }; + +async function transformTextMessageAsync( + message: BaseMessage, + transformer: (text: string) => Promise +): Promise { + if (typeof message.content === "string") { + message.content = await transformer(message.content); + return message; + } + + const redactedContent = await Promise.all( + message.content.map(async (m) => { + if (m.type === "text") { + return { + ...m, + text: await transformer(m.text), + }; + } else { + return Promise.resolve(m); + } + }) + ); + message.content = redactedContent; + return message; +} + +function transformTextMessage( + message: BaseMessage, + transformer: (text: string) => string +): BaseMessage { + if (typeof message.content === "string") { + message.content = transformer(message.content); + return message; + } + + const redactedContent = message.content.map((m) => { + if (m.type === "text") { + return { + ...m, + text: transformer(m.text), + }; + } else { + return m; + } + }); + message.content = redactedContent; + return message; +} + +export class ArcjetRedact< + Detect extends DetectSensitiveInfoEntities | undefined, + CustomEntities extends string +> extends BaseChatModel { + static lc_name() { + return "ArcjetRedact"; + } + + chatModel: BaseChatModel; + + entities?: ValidEntities; + + contextWindowSize?: number; + + detect?: Detect; + + replace?: (entity: ValidEntities[number]) => string | undefined; + + index: number; + + constructor(options: ArcjetRedactOptions) { + super(options); + + if (options.entities && options.entities.length === 0) { + throw new Error("no entities configured for redaction"); + } + + this.chatModel = options.chatModel; + this.entities = options.entities; + this.contextWindowSize = options.contextWindowSize; + this.detect = options.detect; + this.replace = options.replace; + this.index = 0; + } + + _createUniqueReplacement(entity: ValidEntities[number]): string { + const userReplacement = + typeof this.replace !== "undefined" ? this.replace(entity) : undefined; + if (typeof userReplacement !== "undefined") { + return userReplacement; + } + + this.index++; + + if (entity === "email") { + return ``; + } + + if (entity === "phone-number") { + return ``; + } + + if (entity === "ip-address") { + return ``; + } + + if (entity === "credit-card-number") { + return ``; + } + + return ``; + } + + _llmType() { + return "arcjet_redact"; + } + + async _generate( + messages: BaseMessage[], + options: this["ParsedCallOptions"], + runManager?: CallbackManagerForLLMRun | undefined + ): Promise { + const ajOptions: RedactOptions = { + entities: this.entities, + contextWindowSize: this.contextWindowSize, + detect: this.detect, + replace: this._createUniqueReplacement.bind(this), + }; + + const unredactors: Array<(message: string) => string> = []; + // Support CommonJS + const { redact } = await import("@arcjet/redact"); + const redacted = await Promise.all( + messages.map(async (message) => { + return await transformTextMessageAsync(message, async (message) => { + const [redacted, unredact] = await redact(message, ajOptions); + unredactors.push(unredact); + return redacted; + }); + }) + ); + + const response = await this.chatModel._generate( + redacted, + options, + runManager + ); + + return { + ...response, + generations: response.generations.map((resp) => { + return { + ...resp, + message: transformTextMessage(resp.message, (message: string) => { + for (const unredact of unredactors) { + message = unredact(message); + } + return message; + }), + }; + }), + }; + } +} diff --git a/libs/langchain-community/src/chat_models/tests/chatarcjet.test.ts b/libs/langchain-community/src/chat_models/tests/chatarcjet.test.ts new file mode 100644 index 000000000000..759e6ae12697 --- /dev/null +++ b/libs/langchain-community/src/chat_models/tests/chatarcjet.test.ts @@ -0,0 +1,263 @@ +/* eslint-disable no-process-env */ +/* eslint-disable @typescript-eslint/no-non-null-assertion */ + +import { BaseChatModel } from "@langchain/core/language_models/chat_models"; +import { CallbackManagerForLLMRun } from "@langchain/core/callbacks/manager"; +import { BaseMessage, MessageType } from "@langchain/core/messages"; +import { ChatGeneration, ChatResult } from "@langchain/core/outputs"; +import { ArcjetRedact } from "../arcjet.js"; + +class MockChatModel extends BaseChatModel { + callback?: (input: BaseMessage[]) => ChatGeneration[]; + + constructor(callback?: (input: BaseMessage[]) => ChatGeneration[]) { + super({}); + this.callback = callback; + } + + _llmType(): string { + return "mock_chat_model"; + } + + async _generate( + messages: BaseMessage[], + _options: this["ParsedCallOptions"], + _runManager?: CallbackManagerForLLMRun | undefined + ): Promise { + return { + generations: this.callback ? this.callback(messages) : [], + }; + } +} + +class GenericMessage extends BaseMessage { + constructor(text: string) { + super(text); + } + + _getType(): MessageType { + return "human"; + } +} + +test("It passes messages through correctly", async () => { + const generationA = { + message: new GenericMessage("this is the output"), + text: "this is the output", + }; + + const callback = (input: BaseMessage[]) => { + expect(input[0].content).toEqual("this is the input"); + expect(input[1].content).toEqual("this is a second input"); + return [generationA]; + }; + const mockLLM = new MockChatModel(callback); + const options = { + chatModel: mockLLM, + }; + + const arcjetRedact = new ArcjetRedact(options); + const output = await arcjetRedact.invoke([ + "this is the input", + "this is a second input", + ]); + + expect(output.content).toEqual("this is the output"); +}); + +test("It passes messages through correctly in the streaming interface", async () => { + const generationA = { + message: new GenericMessage("this is the output"), + text: "this is the output", + }; + + const callback = (input: BaseMessage[]) => { + expect(input[0].content).toEqual("this is the input"); + expect(input[1].content).toEqual("this is a second input"); + return [generationA]; + }; + const mockLLM = new MockChatModel(callback); + const options = { + chatModel: mockLLM, + }; + + const arcjetRedact = new ArcjetRedact(options); + const stream = await arcjetRedact.stream([ + "this is the input", + "this is a second input", + ]); + + const output = await stream.next(); + + expect(output.value.content).toEqual("this is the output"); +}); + +test("It redacts built in entities across multiple messages and unredacts them in the response", async () => { + const generationA = { + message: new GenericMessage( + "Your email is and your card number is " + ), + text: "Your email is and your card number is ", + }; + + const callback = (input: BaseMessage[]) => { + expect(input[0].content).toEqual("my email address is "); + expect(input[1].content).toEqual( + "my card number is " + ); + return [generationA]; + }; + + const mockLLM = new MockChatModel(callback); + const options = { + chatModel: mockLLM, + }; + + const arcjetRedact = new ArcjetRedact(options); + const output = await arcjetRedact.stream([ + "my email address is test@example.com", + "my card number is 4242424242424242", + ]); + + const first = await output.next(); + expect(first.value.content).toEqual( + "Your email is test@example.com and your card number is 4242424242424242" + ); +}); + +test("it redacts and unredacts correctly", async () => { + const generationA = { + message: new GenericMessage( + "Your email is and your card number is " + ), + text: "Your email is and your card number is ", + }; + + const callback = (input: BaseMessage[]) => { + expect(input[0].content).toEqual("my email address is "); + expect(input[1].content).toEqual( + "my card number is " + ); + return [generationA]; + }; + + const mockLLM = new MockChatModel(callback); + const options = { + chatModel: mockLLM, + }; + + const arcjetRedact = new ArcjetRedact(options); + const output = await arcjetRedact.stream([ + "my email address is test@example.com", + "my card number is 4242424242424242", + ]); + + const first = await output.next(); + expect(first.value.content).toEqual( + "Your email is test@example.com and your card number is 4242424242424242" + ); +}); + +test("it redacts and unredacts correctly", async () => { + const generationA = { + message: new GenericMessage( + "Your email is and your card number is " + ), + text: "Your email is and your card number is ", + }; + + const callback = (input: BaseMessage[]) => { + expect(input[0].content).toEqual("my email address is "); + expect(input[1].content).toEqual( + "my card number is " + ); + return [generationA]; + }; + + const mockLLM = new MockChatModel(callback); + const options = { + chatModel: mockLLM, + }; + + const arcjetRedact = new ArcjetRedact(options); + const output = await arcjetRedact.invoke([ + "my email address is test@example.com", + "my card number is 4242424242424242", + ]); + + expect(output.content).toEqual( + "Your email is test@example.com and your card number is 4242424242424242" + ); +}); + +test("it handles custom detect functions correctly", async () => { + const generationA = { + message: new GenericMessage("custom "), + text: "custom ", + }; + + const callback = (input: BaseMessage[]) => { + expect(input[0].content).toEqual("custom "); + return [generationA]; + }; + + const mockLLM = new MockChatModel(callback); + const customDetector = (tokens: string[]) => { + return tokens.map((t) => + t === "my-custom-string-to-be-detected" ? "custom-entity" : undefined + ); + }; + const options = { + chatModel: mockLLM, + entities: ["custom-entity" as const], + detect: customDetector, + }; + + const arcjetRedact = new ArcjetRedact(options); + const output = await arcjetRedact.invoke([ + "custom my-custom-string-to-be-detected", + ]); + + expect(output.content).toEqual("custom my-custom-string-to-be-detected"); +}); + +test("it handles custom replace functions correctly", async () => { + const generationA = { + message: new GenericMessage( + "custom is email is redacted@example.com" + ), + text: "custom is email is redacted@example.com", + }; + + const callback = (input: BaseMessage[]) => { + expect(input[0].content).toEqual( + "custom email redacted@example.com" + ); + return [generationA]; + }; + + const mockLLM = new MockChatModel(callback); + const customDetector = (tokens: string[]) => { + return tokens.map((t) => + t === "my-custom-string-to-be-detected" ? "custom-entity" : undefined + ); + }; + const customReplacer = (detected: string) => { + return detected === "email" ? "redacted@example.com" : undefined; + }; + const options = { + chatModel: mockLLM, + entities: ["custom-entity" as const, "email" as const], + detect: customDetector, + replace: customReplacer, + }; + + const arcjetRedact = new ArcjetRedact(options); + const output = await arcjetRedact.invoke([ + "custom my-custom-string-to-be-detected email test@example.com", + ]); + + expect(output.content).toEqual( + "custom is my-custom-string-to-be-detected email is test@example.com" + ); +}); diff --git a/libs/langchain-community/src/llms/arcjet.ts b/libs/langchain-community/src/llms/arcjet.ts new file mode 100644 index 000000000000..3c8c0aad9a93 --- /dev/null +++ b/libs/langchain-community/src/llms/arcjet.ts @@ -0,0 +1,80 @@ +import { + LLM, + BaseLLM, + type BaseLLMCallOptions, +} from "@langchain/core/language_models/llms"; +import type { ArcjetSensitiveInfoType, RedactOptions } from "@arcjet/redact"; + +type DetectSensitiveInfoEntities = ( + tokens: string[] +) => Array; +type ValidEntities = Array< + undefined extends Detect + ? ArcjetSensitiveInfoType + : Detect extends DetectSensitiveInfoEntities + ? ArcjetSensitiveInfoType | CustomEntities + : never +>; + +export type { ArcjetSensitiveInfoType, RedactOptions }; + +export interface ArcjetRedactOptions extends BaseLLMCallOptions { + llm: BaseLLM; + entities?: ValidEntities; + contextWindowSize?: number; + detect?: Detect; + replace?: (entity: ValidEntities[number]) => string | undefined; +} + +export class ArcjetRedact< + Detect extends DetectSensitiveInfoEntities | undefined, + CustomEntities extends string +> extends LLM { + static lc_name() { + return "ArcjetRedact"; + } + + llm: BaseLLM; + + entities?: ValidEntities; + + contextWindowSize?: number; + + detect?: Detect; + + replace?: (entity: ValidEntities[number]) => string | undefined; + + constructor(options: ArcjetRedactOptions) { + super(options); + + if (options.entities && options.entities.length === 0) { + throw new Error("no entities configured for redaction"); + } + this.llm = options.llm; + this.entities = options.entities; + this.contextWindowSize = options.contextWindowSize; + this.detect = options.detect; + this.replace = options.replace; + } + + _llmType() { + return "arcjet_redact"; + } + + async _call(input: string, options?: BaseLLMCallOptions): Promise { + const ajOptions: RedactOptions = { + entities: this.entities, + contextWindowSize: this.contextWindowSize, + detect: this.detect, + replace: this.replace, + }; + + const { redact } = await import("@arcjet/redact"); + const [redacted, unredact] = await redact(input, ajOptions); + + // Invoke the underlying LLM with the prompt and options + const result = await this.llm.invoke(redacted, options); + + return unredact(result); + } +} diff --git a/libs/langchain-community/src/llms/tests/arcjet.test.ts b/libs/langchain-community/src/llms/tests/arcjet.test.ts new file mode 100644 index 000000000000..f3ca19c21ee7 --- /dev/null +++ b/libs/langchain-community/src/llms/tests/arcjet.test.ts @@ -0,0 +1,181 @@ +import { test } from "@jest/globals"; +import { + LLM, + type BaseLLMCallOptions, +} from "@langchain/core/language_models/llms"; +import { ArcjetRedact } from "../arcjet.js"; + +// Mock LLM for testing purposes +export class MockLLM extends LLM { + static lc_name() { + return "MockLLM"; + } + + lc_serializable = true; + + callback?: (input: string) => string; + + constructor(callback?: (input: string) => string) { + super({}); + this.callback = callback; + } + + _llmType() { + return "mock_llm"; + } + + async _call(input: string, _options?: BaseLLMCallOptions): Promise { + if (typeof this.callback !== "undefined") { + return this.callback(input); + } else { + throw new Error("no callback"); + } + } +} + +test("It calls the base LLM correctly", async () => { + const callback = (input: string) => { + expect(input).toEqual("this is the input"); + return "this is the output"; + }; + const mockLLM = new MockLLM(callback); + const options = { + llm: mockLLM, + }; + + const arcjetRedact = new ArcjetRedact(options); + const output = await arcjetRedact.invoke("this is the input"); + + expect(output).toEqual("this is the output"); +}); + +test("It performs redactions and unredactions", async () => { + const callback = (input: string) => { + expect(input).toEqual("email "); + return "your email is "; + }; + const mockLLM = new MockLLM(callback); + const options = { + llm: mockLLM, + }; + + const arcjetRedact = new ArcjetRedact(options); + const output = await arcjetRedact.invoke("email test@example.com"); + + expect(output).toEqual("your email is test@example.com"); +}); + +test("It only redacts configured entities", async () => { + const callback = (input: string) => { + expect(input).toEqual( + "email test@example.com phone " + ); + return "your phone number is "; + }; + const mockLLM = new MockLLM(callback); + const options = { + llm: mockLLM, + entities: ["phone-number" as const], + }; + + const arcjetRedact = new ArcjetRedact(options); + const output = await arcjetRedact.invoke( + "email test@example.com phone +35312345678" + ); + + expect(output).toEqual("your phone number is +35312345678"); +}); + +test("It redacts custom entities", async () => { + const callback = (input: string) => { + expect(input).toEqual("custom "); + return "custom is "; + }; + const mockLLM = new MockLLM(callback); + const customDetector = (tokens: string[]) => { + return tokens.map((t) => + t === "my-custom-string-to-be-detected" ? "custom-entity" : undefined + ); + }; + const options = { + llm: mockLLM, + entities: ["custom-entity" as const], + detect: customDetector, + }; + + const arcjetRedact = new ArcjetRedact(options); + const output = await arcjetRedact.invoke( + "custom my-custom-string-to-be-detected" + ); + + expect(output).toEqual("custom is my-custom-string-to-be-detected"); +}); + +test("It provides the correct number of tokens to the context window", async () => { + const callback = (input: string) => { + expect(input).toEqual("this is a sentence for testing"); + return "this is a sentence for testing"; + }; + const mockLLM = new MockLLM(callback); + const customDetector = (tokens: string[]) => { + expect(tokens).toHaveLength(4); + return tokens.map(() => undefined); + }; + const options = { + llm: mockLLM, + entities: ["email" as const], + detect: customDetector, + contextWindowSize: 4, + }; + + const arcjetRedact = new ArcjetRedact(options); + const output = await arcjetRedact.invoke("this is a sentence for testing"); + + expect(output).toEqual("this is a sentence for testing"); +}); + +test("It uses custom replacers", async () => { + const callback = (input: string) => { + expect(input).toEqual( + "custom email redacted@example.com" + ); + return "custom is email is redacted@example.com"; + }; + const mockLLM = new MockLLM(callback); + const customDetector = (tokens: string[]) => { + return tokens.map((t) => + t === "my-custom-string-to-be-detected" ? "custom-entity" : undefined + ); + }; + const customReplacer = (detected: string) => { + return detected === "email" ? "redacted@example.com" : undefined; + }; + const options = { + llm: mockLLM, + entities: ["custom-entity" as const, "email" as const], + detect: customDetector, + replace: customReplacer, + }; + + const arcjetRedact = new ArcjetRedact(options); + const output = await arcjetRedact.invoke( + "custom my-custom-string-to-be-detected email test@example.com" + ); + + expect(output).toEqual( + "custom is my-custom-string-to-be-detected email is test@example.com" + ); +}); + +test("It throws when no entities are configured", async () => { + const mockLLM = new MockLLM(); + const options = { + llm: mockLLM, + entities: [], + }; + + expect(() => { + // eslint-disable-next-line no-new + new ArcjetRedact(options); + }).toThrow("no entities configured for redaction"); +}); diff --git a/libs/langchain-community/src/load/import_constants.ts b/libs/langchain-community/src/load/import_constants.ts index 25ef64e5bac1..8b585f599afe 100644 --- a/libs/langchain-community/src/load/import_constants.ts +++ b/libs/langchain-community/src/load/import_constants.ts @@ -23,6 +23,7 @@ export const optionalImportEntrypoints: string[] = [ "langchain_community/embeddings/tencent_hunyuan", "langchain_community/embeddings/tencent_hunyuan/web", "langchain_community/embeddings/zhipuai", + "langchain_community/llms/arcjet", "langchain_community/llms/bedrock", "langchain_community/llms/bedrock/web", "langchain_community/llms/cohere", @@ -82,6 +83,7 @@ export const optionalImportEntrypoints: string[] = [ "langchain_community/vectorstores/xata", "langchain_community/vectorstores/zep", "langchain_community/vectorstores/zep_cloud", + "langchain_community/chat_models/arcjet", "langchain_community/chat_models/bedrock", "langchain_community/chat_models/bedrock/web", "langchain_community/chat_models/googlevertexai", diff --git a/yarn.lock b/yarn.lock index 6a779f772556..d68702f3d306 100644 --- a/yarn.lock +++ b/yarn.lock @@ -286,6 +286,22 @@ __metadata: languageName: node linkType: hard +"@arcjet/redact-wasm@npm:1.0.0-alpha.24": + version: 1.0.0-alpha.24 + resolution: "@arcjet/redact-wasm@npm:1.0.0-alpha.24" + checksum: b0cf2a5b8eec1fcacdc130c3fce963cfe2bdc786aeb21296409b8bcc0053204ea6c1d7a816e9adf999fb56c8f73aaa950dfbbb7bd628d06ea21fb868cc4b7e5c + languageName: node + linkType: hard + +"@arcjet/redact@npm:^v1.0.0-alpha.23": + version: 1.0.0-alpha.24 + resolution: "@arcjet/redact@npm:1.0.0-alpha.24" + dependencies: + "@arcjet/redact-wasm": 1.0.0-alpha.24 + checksum: 06eedc5f65eb98ac3b3f2598981a3c4b7265f89a59b138dfb38c7e19e3c4342ccb5ddfc3a0ae94add47f931b095138794b8c88355003c27374c37ead02f2106c + languageName: node + linkType: hard + "@aws-crypto/crc32@npm:3.0.0": version: 3.0.0 resolution: "@aws-crypto/crc32@npm:3.0.0" @@ -11174,6 +11190,7 @@ __metadata: version: 0.0.0-use.local resolution: "@langchain/community@workspace:libs/langchain-community" dependencies: + "@arcjet/redact": ^v1.0.0-alpha.23 "@aws-crypto/sha256-js": ^5.0.0 "@aws-sdk/client-bedrock-agent-runtime": ^3.583.0 "@aws-sdk/client-bedrock-runtime": ^3.422.0 @@ -11349,6 +11366,7 @@ __metadata: zod: ^3.22.3 zod-to-json-schema: ^3.22.5 peerDependencies: + "@arcjet/redact": ^v1.0.0-alpha.23 "@aws-crypto/sha256-js": ^5.0.0 "@aws-sdk/client-bedrock-agent-runtime": ^3.583.0 "@aws-sdk/client-bedrock-runtime": ^3.422.0 @@ -11470,6 +11488,8 @@ __metadata: youtube-transcript: ^1.0.6 youtubei.js: ^9.1.0 peerDependenciesMeta: + "@arcjet/redact": + optional: true "@aws-crypto/sha256-js": optional: true "@aws-sdk/client-bedrock-agent-runtime": From 34421e04e6b0aea8f84bdd2bb9c74e35f206214a Mon Sep 17 00:00:00 2001 From: Raj Mehta Date: Tue, 10 Sep 2024 15:38:42 -0700 Subject: [PATCH 153/344] feat(community): BM25 Keyword Search Retriever (#6719) Co-authored-by: jacoblee93 --- .../docs/integrations/retrievers/bm25.ipynb | 101 ++++++++++++++++++ libs/langchain-community/.gitignore | 4 + libs/langchain-community/langchain.config.js | 1 + libs/langchain-community/package.json | 13 +++ .../src/load/import_map.ts | 1 + .../src/retrievers/bm25.ts | 58 ++++++++++ .../src/retrievers/tests/bm25.test.ts | 27 +++++ .../src/utils/@furkantoprak/bm25/BM25.ts | 100 +++++++++++++++++ .../src/utils/@furkantoprak/bm25/LICENSE.md | 21 ++++ 9 files changed, 326 insertions(+) create mode 100644 docs/core_docs/docs/integrations/retrievers/bm25.ipynb create mode 100644 libs/langchain-community/src/retrievers/bm25.ts create mode 100644 libs/langchain-community/src/retrievers/tests/bm25.test.ts create mode 100644 libs/langchain-community/src/utils/@furkantoprak/bm25/BM25.ts create mode 100644 libs/langchain-community/src/utils/@furkantoprak/bm25/LICENSE.md diff --git a/docs/core_docs/docs/integrations/retrievers/bm25.ipynb b/docs/core_docs/docs/integrations/retrievers/bm25.ipynb new file mode 100644 index 000000000000..b106554c6b40 --- /dev/null +++ b/docs/core_docs/docs/integrations/retrievers/bm25.ipynb @@ -0,0 +1,101 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# BM25\n", + "\n", + "BM25, also known as [Okapi BM25](https://en.wikipedia.org/wiki/Okapi_BM25), is a ranking function used in information retrieval systems to estimate the relevance of documents to a given search query.\n", + "\n", + "You can use it as part of your retrieval pipeline as a to rerank documents as a postprocessing step after retrieving an initial set of documents from another source.\n", + "\n", + "## Setup\n", + "\n", + "The `BM25Retriever` is exported from `@langchain/community`. You'll need to install it like this:\n", + "\n", + "```{=mdx}\n", + "import IntegrationInstallTooltip from \"@mdx_components/integration_install_tooltip.mdx\";\n", + "import Npm2Yarn from \"@theme/Npm2Yarn\";\n", + "\n", + "\n", + "\n", + "\n", + " @langchain/community @langchain/core\n", + "\n", + "```\n", + "\n", + "This retriever uses code from [`this implementation`](https://github.com/FurkanToprak/OkapiBM25) of Okapi BM25.\n", + "\n", + "## Usage\n", + "\n", + "You can now create a new retriever with previously retrieved documents:" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[\n", + " { pageContent: 'mitochondria is made of lipids', metadata: {} },\n", + " {\n", + " pageContent: 'mitochondria is the powerhouse of the cell',\n", + " metadata: {}\n", + " },\n", + " { pageContent: 'Buildings are made out of brick', metadata: {} },\n", + " { pageContent: 'Buildings are made out of wood', metadata: {} }\n", + "]\n" + ] + } + ], + "source": [ + "import { BM25Retriever } from \"@langchain/community/retrievers/bm25\";\n", + "\n", + "const retriever = BM25Retriever.fromDocuments([\n", + " { pageContent: \"Buildings are made out of brick\", metadata: {} },\n", + " { pageContent: \"Buildings are made out of wood\", metadata: {} },\n", + " { pageContent: \"Buildings are made out of stone\", metadata: {} },\n", + " { pageContent: \"Cars are made out of metal\", metadata: {} },\n", + " { pageContent: \"Cars are made out of plastic\", metadata: {} },\n", + " { pageContent: \"mitochondria is the powerhouse of the cell\", metadata: {} },\n", + " { pageContent: \"mitochondria is made of lipids\", metadata: {} },\n", + "], { k: 4 });\n", + "\n", + "// Will return the 4 documents reranked by the BM25 algorithm\n", + "await retriever.invoke(\"mitochondria\");" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "TypeScript", + "language": "typescript", + "name": "tslab" + }, + "language_info": { + "codemirror_mode": { + "mode": "typescript", + "name": "javascript", + "typescript": true + }, + "file_extension": ".ts", + "mimetype": "text/typescript", + "name": "typescript", + "version": "3.7.2" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/libs/langchain-community/.gitignore b/libs/langchain-community/.gitignore index 299b955d097a..1bb10382f52e 100644 --- a/libs/langchain-community/.gitignore +++ b/libs/langchain-community/.gitignore @@ -626,6 +626,10 @@ retrievers/amazon_knowledge_base.cjs retrievers/amazon_knowledge_base.js retrievers/amazon_knowledge_base.d.ts retrievers/amazon_knowledge_base.d.cts +retrievers/bm25.cjs +retrievers/bm25.js +retrievers/bm25.d.ts +retrievers/bm25.d.cts retrievers/chaindesk.cjs retrievers/chaindesk.js retrievers/chaindesk.d.ts diff --git a/libs/langchain-community/langchain.config.js b/libs/langchain-community/langchain.config.js index 5c9818185cec..c4a964f747c4 100644 --- a/libs/langchain-community/langchain.config.js +++ b/libs/langchain-community/langchain.config.js @@ -196,6 +196,7 @@ export const config = { // retrievers "retrievers/amazon_kendra": "retrievers/amazon_kendra", "retrievers/amazon_knowledge_base": "retrievers/amazon_knowledge_base", + "retrievers/bm25": "retrievers/bm25", "retrievers/chaindesk": "retrievers/chaindesk", "retrievers/databerry": "retrievers/databerry", "retrievers/dria": "retrievers/dria", diff --git a/libs/langchain-community/package.json b/libs/langchain-community/package.json index 85444a2936a0..f8d2288a20c6 100644 --- a/libs/langchain-community/package.json +++ b/libs/langchain-community/package.json @@ -2118,6 +2118,15 @@ "import": "./retrievers/amazon_knowledge_base.js", "require": "./retrievers/amazon_knowledge_base.cjs" }, + "./retrievers/bm25": { + "types": { + "import": "./retrievers/bm25.d.ts", + "require": "./retrievers/bm25.d.cts", + "default": "./retrievers/bm25.d.ts" + }, + "import": "./retrievers/bm25.js", + "require": "./retrievers/bm25.cjs" + }, "./retrievers/chaindesk": { "types": { "import": "./retrievers/chaindesk.d.ts", @@ -3713,6 +3722,10 @@ "retrievers/amazon_knowledge_base.js", "retrievers/amazon_knowledge_base.d.ts", "retrievers/amazon_knowledge_base.d.cts", + "retrievers/bm25.cjs", + "retrievers/bm25.js", + "retrievers/bm25.d.ts", + "retrievers/bm25.d.cts", "retrievers/chaindesk.cjs", "retrievers/chaindesk.js", "retrievers/chaindesk.d.ts", diff --git a/libs/langchain-community/src/load/import_map.ts b/libs/langchain-community/src/load/import_map.ts index 59efd56b760e..5bbd9e4d0a01 100644 --- a/libs/langchain-community/src/load/import_map.ts +++ b/libs/langchain-community/src/load/import_map.ts @@ -54,6 +54,7 @@ export * as chat_models__moonshot from "../chat_models/moonshot.js"; export * as chat_models__ollama from "../chat_models/ollama.js"; export * as chat_models__togetherai from "../chat_models/togetherai.js"; export * as chat_models__yandex from "../chat_models/yandex.js"; +export * as retrievers__bm25 from "../retrievers/bm25.js"; export * as retrievers__chaindesk from "../retrievers/chaindesk.js"; export * as retrievers__databerry from "../retrievers/databerry.js"; export * as retrievers__remote from "../retrievers/remote/index.js"; diff --git a/libs/langchain-community/src/retrievers/bm25.ts b/libs/langchain-community/src/retrievers/bm25.ts new file mode 100644 index 000000000000..dfc04709cba1 --- /dev/null +++ b/libs/langchain-community/src/retrievers/bm25.ts @@ -0,0 +1,58 @@ +import { BaseRetriever, BaseRetrieverInput } from "@langchain/core/retrievers"; +import { Document } from "@langchain/core/documents"; + +import { BM25 } from "../utils/@furkantoprak/bm25/BM25.js"; + +export type BM25RetrieverOptions = { + docs: Document[]; + k: number; +} & BaseRetrieverInput; + +/** + * A retriever that uses the BM25 algorithm to rank documents based on their + * similarity to a query. It uses the "okapibm25" package for BM25 scoring. + * The k parameter determines the number of documents to return for each query. + */ +export class BM25Retriever extends BaseRetriever { + static lc_name() { + return "BM25Retriever"; + } + + lc_namespace = ["langchain", "retrievers", "bm25_retriever"]; + + static fromDocuments( + documents: Document[], + options: Omit + ) { + return new this({ ...options, docs: documents }); + } + + docs: Document[]; + + k: number; + + constructor(options: BM25RetrieverOptions) { + super(options); + this.docs = options.docs; + this.k = options.k; + } + + private preprocessFunc(text: string): string[] { + return text.toLowerCase().split(/\s+/); + } + + async _getRelevantDocuments(query: string) { + const processedQuery = this.preprocessFunc(query); + const documents = this.docs.map((doc) => doc.pageContent); + const scores = BM25(documents, processedQuery) as number[]; + + const scoredDocs = this.docs.map((doc, index) => ({ + document: doc, + score: scores[index], + })); + + scoredDocs.sort((a, b) => b.score - a.score); + + return scoredDocs.slice(0, this.k).map((item) => item.document); + } +} diff --git a/libs/langchain-community/src/retrievers/tests/bm25.test.ts b/libs/langchain-community/src/retrievers/tests/bm25.test.ts new file mode 100644 index 000000000000..bcfe46f940b8 --- /dev/null +++ b/libs/langchain-community/src/retrievers/tests/bm25.test.ts @@ -0,0 +1,27 @@ +import { expect, test } from "@jest/globals"; +import { Document } from "@langchain/core/documents"; +import { BM25Retriever } from "../bm25.js"; + +test("BM25Retriever", async () => { + const docs = [ + new Document({ + pageContent: "The quick brown fox jumps over the lazy dog", + }), + new Document({ + pageContent: "A lazy dog sleeps all day", + }), + new Document({ + pageContent: "The brown fox is quick and clever", + }), + ]; + + const retriever = BM25Retriever.fromDocuments(docs, { + k: 2, + }); + const results = await retriever.invoke("the fox and the dog"); + + expect(results).toHaveLength(2); + expect(results[0].pageContent).toBe( + "The quick brown fox jumps over the lazy dog" + ); +}); diff --git a/libs/langchain-community/src/utils/@furkantoprak/bm25/BM25.ts b/libs/langchain-community/src/utils/@furkantoprak/bm25/BM25.ts new file mode 100644 index 000000000000..e3e2621168e7 --- /dev/null +++ b/libs/langchain-community/src/utils/@furkantoprak/bm25/BM25.ts @@ -0,0 +1,100 @@ +/** + * Adapted from + * https://github.com/FurkanToprak/OkapiBM25 + * + * Inlined due to CJS import issues. + */ + +/** Gets word count. */ +export const getWordCount = (corpus: string) => { + return ((corpus || "").match(/\w+/g) || []).length; +}; + +/** Number of occurences of a word in a string. */ +export const getTermFrequency = (term: string, corpus: string) => { + return ((corpus || "").match(new RegExp(term, "g")) || []).length; +}; + +/** Inverse document frequency. */ +export const getIDF = (term: string, documents: string[]) => { + // Number of relevant documents. + const relevantDocuments = documents.filter((document: string) => + document.includes(term) + ).length; + return Math.log( + (documents.length - relevantDocuments + 0.5) / (relevantDocuments + 0.5) + 1 + ); +}; + +/** Represents a document; useful when sorting results. + */ +export interface BMDocument { + /** The document is originally scoreed. */ + document: string; + /** The score that the document recieves. */ + score: number; +} + +/** Constants that are free parameters used in BM25, specifically when generating inverse document frequency. */ +export interface BMConstants { + /** Free parameter. Is 0.75 by default. */ + b?: number; + /** Free parameter. Is 1.2 by default. Generally in range [1.2, 2.0] */ + k1?: number; +} + +/** If returns positive, the sorting results in secondEl coming before firstEl, else, firstEl comes before secondEL */ +export type BMSorter = (firstEl: BMDocument, secondEl: BMDocument) => number; + +/** Implementation of Okapi BM25 algorithm. + * @param documents: Collection of documents. + * @param keywords: query terms. + * @param constants: Contains free parameters k1 and b. b=0.75 and k1=1.2 by default. + * @param sort: A function that allows you to sort queries by a given rule. If not provided, returns results corresponding to the original order. + * If this option is provided, the return type will not be an array of scores but an array of documents with their scores. + */ +export function BM25( + documents: string[], + keywords: string[], + constants?: BMConstants, + sorter?: BMSorter +): number[] | BMDocument[] { + const b = constants && constants.b ? constants.b : 0.75; + const k1 = constants && constants.k1 ? constants.k1 : 1.2; + const documentLengths = documents.map((document: string) => + getWordCount(document) + ); + const averageDocumentLength = + documentLengths.reduce((a, b) => a + b, 0) / documents.length; + const idfByKeyword = keywords.reduce((obj, keyword) => { + obj.set(keyword, getIDF(keyword, documents)); + return obj; + }, new Map()); + + const scores = documents.map((document: string, index: number) => { + const score = keywords + .map((keyword: string) => { + const inverseDocumentFrequency = idfByKeyword.get(keyword); + if (inverseDocumentFrequency === undefined) { + throw new Error("Missing keyword."); + } + const termFrequency = getTermFrequency(keyword, document); + const documentLength = documentLengths[index]; + return ( + (inverseDocumentFrequency * (termFrequency * (k1 + 1))) / + (termFrequency + + k1 * (1 - b + (b * documentLength) / averageDocumentLength)) + ); + }) + .reduce((a: number, b: number) => a + b, 0); + if (sorter) { + return { score, document } as BMDocument; + } + return score; + }); + // sort the results + if (sorter) { + return (scores as BMDocument[]).sort(sorter); + } + return scores as number[]; +} diff --git a/libs/langchain-community/src/utils/@furkantoprak/bm25/LICENSE.md b/libs/langchain-community/src/utils/@furkantoprak/bm25/LICENSE.md new file mode 100644 index 000000000000..8dd59105dd36 --- /dev/null +++ b/libs/langchain-community/src/utils/@furkantoprak/bm25/LICENSE.md @@ -0,0 +1,21 @@ +# MIT License + +## Copyright (c) 2020 Furkan Toprak + +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. From e5b5cd80f62908f0f85884ca1281625bbe7b9dd2 Mon Sep 17 00:00:00 2001 From: Jacob Lee Date: Tue, 10 Sep 2024 15:39:02 -0700 Subject: [PATCH 154/344] chore(baidu-qianfan): Release 0.0.4 (#6726) --- libs/langchain-baidu-qianfan/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libs/langchain-baidu-qianfan/package.json b/libs/langchain-baidu-qianfan/package.json index e6648d52a569..1de6beb41ab3 100644 --- a/libs/langchain-baidu-qianfan/package.json +++ b/libs/langchain-baidu-qianfan/package.json @@ -1,6 +1,6 @@ { "name": "@langchain/baidu-qianfan", - "version": "0.0.3", + "version": "0.0.4", "description": "Baidu Qianfan integration for LangChain.js", "type": "module", "engines": { From baa0d7b25fae080580b4cb9b170ebc946e65b76b Mon Sep 17 00:00:00 2001 From: Jacob Lee Date: Tue, 10 Sep 2024 15:42:06 -0700 Subject: [PATCH 155/344] chore(anthropic): Release 0.2.18 (#6727) --- libs/langchain-anthropic/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libs/langchain-anthropic/package.json b/libs/langchain-anthropic/package.json index 2bce3129b107..d2c27b0aeb4d 100644 --- a/libs/langchain-anthropic/package.json +++ b/libs/langchain-anthropic/package.json @@ -1,6 +1,6 @@ { "name": "@langchain/anthropic", - "version": "0.2.17", + "version": "0.2.18", "description": "Anthropic integrations for LangChain.js", "type": "module", "engines": { From 60601bc12adafa505ef32bd76d107d5d25913e44 Mon Sep 17 00:00:00 2001 From: Jacob Lee Date: Tue, 10 Sep 2024 15:50:02 -0700 Subject: [PATCH 156/344] chore(core): Release 0.2.32 (#6728) --- 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 9bc186af33be..e035b5197e98 100644 --- a/langchain-core/package.json +++ b/langchain-core/package.json @@ -1,6 +1,6 @@ { "name": "@langchain/core", - "version": "0.2.31", + "version": "0.2.32", "description": "Core LangChain.js abstractions and schemas", "type": "module", "engines": { From f1cf907d23fafc45612f3abdf7ab4e62903d3983 Mon Sep 17 00:00:00 2001 From: Jacob Lee Date: Tue, 10 Sep 2024 16:26:36 -0700 Subject: [PATCH 157/344] chore(community): Release 0.2.33 (#6729) --- libs/langchain-community/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libs/langchain-community/package.json b/libs/langchain-community/package.json index f8d2288a20c6..c4c08c23fc1c 100644 --- a/libs/langchain-community/package.json +++ b/libs/langchain-community/package.json @@ -1,6 +1,6 @@ { "name": "@langchain/community", - "version": "0.2.32", + "version": "0.2.33", "description": "Third-party integrations for LangChain.js", "type": "module", "engines": { From 3b892eecb0af4c8ba4d783611f096627b9449ed0 Mon Sep 17 00:00:00 2001 From: James Ross Date: Wed, 11 Sep 2024 01:02:51 +0100 Subject: [PATCH 158/344] cloudflare[minor]: use native Cloudflare Workers AI binding (#5287) Co-authored-by: Brace Sproul Co-authored-by: jacoblee93 --- langchain/package.json | 2 +- libs/langchain-cloudflare/package.json | 3 +-- libs/langchain-cloudflare/src/embeddings.ts | 7 +++---- yarn.lock | 20 ++++++-------------- 4 files changed, 11 insertions(+), 21 deletions(-) diff --git a/langchain/package.json b/langchain/package.json index 91c99b175d6e..def762229f25 100644 --- a/langchain/package.json +++ b/langchain/package.json @@ -600,7 +600,7 @@ "@aws-sdk/types": "^3.357.0", "@azure/storage-blob": "^12.15.0", "@browserbasehq/sdk": "^1.1.5", - "@cloudflare/workers-types": "^4.20230922.0", + "@cloudflare/workers-types": "^4.20240909.0", "@faker-js/faker": "^7.6.0", "@gomomento/sdk": "^1.51.1", "@gomomento/sdk-core": "^1.51.1", diff --git a/libs/langchain-cloudflare/package.json b/libs/langchain-cloudflare/package.json index d6c9a84ca0d1..e0dd6e229504 100644 --- a/libs/langchain-cloudflare/package.json +++ b/libs/langchain-cloudflare/package.json @@ -35,12 +35,11 @@ "author": "LangChain", "license": "MIT", "dependencies": { - "@cloudflare/ai": "1.0.47", "@langchain/core": ">0.1.0 <0.3.0", "uuid": "^10.0.0" }, "devDependencies": { - "@cloudflare/workers-types": "^4.20231218.0", + "@cloudflare/workers-types": "^4.20240909.0", "@jest/globals": "^29.5.0", "@langchain/langgraph": "~0.0.31", "@langchain/scripts": ">=0.1.0 <0.2.0", diff --git a/libs/langchain-cloudflare/src/embeddings.ts b/libs/langchain-cloudflare/src/embeddings.ts index 1c1ba479f8d5..1db9fb121b6a 100644 --- a/libs/langchain-cloudflare/src/embeddings.ts +++ b/libs/langchain-cloudflare/src/embeddings.ts @@ -1,5 +1,4 @@ -import { Ai } from "@cloudflare/ai"; -import { Fetcher } from "@cloudflare/workers-types"; +import { Ai } from "@cloudflare/workers-types"; import { Embeddings, EmbeddingsParams } from "@langchain/core/embeddings"; import { chunkArray } from "@langchain/core/utils/chunk_array"; @@ -14,7 +13,7 @@ type AiTextEmbeddingsOutput = { export interface CloudflareWorkersAIEmbeddingsParams extends EmbeddingsParams { /** Binding */ - binding: Fetcher; + binding: Ai; /** * Model name to use @@ -57,7 +56,7 @@ export class CloudflareWorkersAIEmbeddings extends Embeddings { "Must supply a Workers AI binding, eg { binding: env.AI }" ); } - this.ai = new Ai(fields.binding); + this.ai = fields.binding; this.modelName = fields?.model ?? fields.modelName ?? this.model; this.model = this.modelName; this.stripNewLines = fields.stripNewLines ?? this.stripNewLines; diff --git a/yarn.lock b/yarn.lock index d68702f3d306..60ad225ca0ee 100644 --- a/yarn.lock +++ b/yarn.lock @@ -8473,13 +8473,6 @@ __metadata: languageName: node linkType: hard -"@cloudflare/ai@npm:1.0.47": - version: 1.0.47 - resolution: "@cloudflare/ai@npm:1.0.47" - checksum: a9e371f789256bb5795c0cb60d975749b07d6cb50141b997da6b5c94380ddf18e851cba518e246414fcab4239d8f4438c5fe29172e6f06c136be3be19a3b65e0 - languageName: node - linkType: hard - "@cloudflare/workers-types@npm:^4.20230922.0": version: 4.20230922.0 resolution: "@cloudflare/workers-types@npm:4.20230922.0" @@ -8487,10 +8480,10 @@ __metadata: languageName: node linkType: hard -"@cloudflare/workers-types@npm:^4.20231218.0": - version: 4.20231218.0 - resolution: "@cloudflare/workers-types@npm:4.20231218.0" - checksum: b7e50a76ee8e9d662227bbb74798b93b6102acc224f1071a9c99a9adb419ad0b3bdabf7561e7e1b4a320a6a4616badeecdfb1848fbdaada197c7b37d845b8774 +"@cloudflare/workers-types@npm:^4.20240909.0": + version: 4.20240909.0 + resolution: "@cloudflare/workers-types@npm:4.20240909.0" + checksum: 82fe9b22510d6a23533830684018651bf8a679692cc487cf82d15816e4c91b95ca5e759a6702d6b1268cd93cda8f0b45f3bf9e6ac27e926112ed4f3c7ef65968 languageName: node linkType: hard @@ -11113,8 +11106,7 @@ __metadata: version: 0.0.0-use.local resolution: "@langchain/cloudflare@workspace:libs/langchain-cloudflare" dependencies: - "@cloudflare/ai": 1.0.47 - "@cloudflare/workers-types": ^4.20231218.0 + "@cloudflare/workers-types": ^4.20240909.0 "@jest/globals": ^29.5.0 "@langchain/core": ">0.1.0 <0.3.0" "@langchain/langgraph": ~0.0.31 @@ -32219,7 +32211,7 @@ __metadata: "@aws-sdk/types": ^3.357.0 "@azure/storage-blob": ^12.15.0 "@browserbasehq/sdk": ^1.1.5 - "@cloudflare/workers-types": ^4.20230922.0 + "@cloudflare/workers-types": ^4.20240909.0 "@faker-js/faker": ^7.6.0 "@gomomento/sdk": ^1.51.1 "@gomomento/sdk-core": ^1.51.1 From 2e6713368e320bc27223707e9ceb6511fcabc76a Mon Sep 17 00:00:00 2001 From: Jacob Lee Date: Tue, 10 Sep 2024 17:05:56 -0700 Subject: [PATCH 159/344] docs: Fix typo in chatbot tutorial (#6730) --- docs/core_docs/docs/tutorials/chatbot.ipynb | 278 +++++++++----------- 1 file changed, 121 insertions(+), 157 deletions(-) diff --git a/docs/core_docs/docs/tutorials/chatbot.ipynb b/docs/core_docs/docs/tutorials/chatbot.ipynb index bec77577e978..d63a1ea3295d 100644 --- a/docs/core_docs/docs/tutorials/chatbot.ipynb +++ b/docs/core_docs/docs/tutorials/chatbot.ipynb @@ -98,52 +98,49 @@ "cell_type": "code", "execution_count": 1, "metadata": {}, + "outputs": [], + "source": [ + "// @lc-docs-hide-cell\n", + "\n", + "import { ChatOpenAI } from \"@langchain/openai\";\n", + "\n", + "const model = new ChatOpenAI({\n", + " model: \"gpt-4o-mini\",\n", + " temperature: 0,\n", + "});" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, "outputs": [ { "data": { "text/plain": [ "AIMessage {\n", - " lc_serializable: \u001b[33mtrue\u001b[39m,\n", - " lc_kwargs: {\n", - " content: \u001b[32m\"Hello Bob, it's nice to meet you! I'm an AI assistant created by Anthropic. How are you doing today?\"\u001b[39m,\n", - " tool_calls: [],\n", - " invalid_tool_calls: [],\n", - " additional_kwargs: {\n", - " id: \u001b[32m\"msg_015Qvu91azZviks5VzGvYT7z\"\u001b[39m,\n", - " type: \u001b[32m\"message\"\u001b[39m,\n", - " role: \u001b[32m\"assistant\"\u001b[39m,\n", - " model: \u001b[32m\"claude-3-sonnet-20240229\"\u001b[39m,\n", - " stop_sequence: \u001b[1mnull\u001b[22m,\n", - " usage: { input_tokens: \u001b[33m12\u001b[39m, output_tokens: \u001b[33m30\u001b[39m },\n", - " stop_reason: \u001b[32m\"end_turn\"\u001b[39m\n", + " \"id\": \"chatcmpl-A64of8iD4GIFNSYlOaFHxPdCeyl9E\",\n", + " \"content\": \"Hi Bob! How can I assist you today?\",\n", + " \"additional_kwargs\": {},\n", + " \"response_metadata\": {\n", + " \"tokenUsage\": {\n", + " \"completionTokens\": 10,\n", + " \"promptTokens\": 11,\n", + " \"totalTokens\": 21\n", " },\n", - " response_metadata: {}\n", - " },\n", - " lc_namespace: [ \u001b[32m\"langchain_core\"\u001b[39m, \u001b[32m\"messages\"\u001b[39m ],\n", - " content: \u001b[32m\"Hello Bob, it's nice to meet you! I'm an AI assistant created by Anthropic. How are you doing today?\"\u001b[39m,\n", - " name: \u001b[90mundefined\u001b[39m,\n", - " additional_kwargs: {\n", - " id: \u001b[32m\"msg_015Qvu91azZviks5VzGvYT7z\"\u001b[39m,\n", - " type: \u001b[32m\"message\"\u001b[39m,\n", - " role: \u001b[32m\"assistant\"\u001b[39m,\n", - " model: \u001b[32m\"claude-3-sonnet-20240229\"\u001b[39m,\n", - " stop_sequence: \u001b[1mnull\u001b[22m,\n", - " usage: { input_tokens: \u001b[33m12\u001b[39m, output_tokens: \u001b[33m30\u001b[39m },\n", - " stop_reason: \u001b[32m\"end_turn\"\u001b[39m\n", + " \"finish_reason\": \"stop\"\n", " },\n", - " response_metadata: {\n", - " id: \u001b[32m\"msg_015Qvu91azZviks5VzGvYT7z\"\u001b[39m,\n", - " model: \u001b[32m\"claude-3-sonnet-20240229\"\u001b[39m,\n", - " stop_sequence: \u001b[1mnull\u001b[22m,\n", - " usage: { input_tokens: \u001b[33m12\u001b[39m, output_tokens: \u001b[33m30\u001b[39m },\n", - " stop_reason: \u001b[32m\"end_turn\"\u001b[39m\n", - " },\n", - " tool_calls: [],\n", - " invalid_tool_calls: []\n", + " \"tool_calls\": [],\n", + " \"invalid_tool_calls\": [],\n", + " \"usage_metadata\": {\n", + " \"input_tokens\": 11,\n", + " \"output_tokens\": 10,\n", + " \"total_tokens\": 21\n", + " }\n", "}" ] }, - "execution_count": 1, + "execution_count": 2, "metadata": {}, "output_type": "execute_result" } @@ -163,54 +160,35 @@ }, { "cell_type": "code", - "execution_count": 2, + "execution_count": 3, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "AIMessage {\n", - " lc_serializable: \u001b[33mtrue\u001b[39m,\n", - " lc_kwargs: {\n", - " content: \u001b[32m\"I'm afraid I don't actually know your name. I'm Claude, an AI assistant created by Anthropic.\"\u001b[39m,\n", - " tool_calls: [],\n", - " invalid_tool_calls: [],\n", - " additional_kwargs: {\n", - " id: \u001b[32m\"msg_01TNDCwsU7ruVoqJwjKqNrzJ\"\u001b[39m,\n", - " type: \u001b[32m\"message\"\u001b[39m,\n", - " role: \u001b[32m\"assistant\"\u001b[39m,\n", - " model: \u001b[32m\"claude-3-sonnet-20240229\"\u001b[39m,\n", - " stop_sequence: \u001b[1mnull\u001b[22m,\n", - " usage: { input_tokens: \u001b[33m12\u001b[39m, output_tokens: \u001b[33m27\u001b[39m },\n", - " stop_reason: \u001b[32m\"end_turn\"\u001b[39m\n", + " \"id\": \"chatcmpl-A64ogC7owxmPla3ggZERNCFZpVHSp\",\n", + " \"content\": \"I'm sorry, but I don't have access to personal information about users unless it has been shared with me in the course of our conversation. If you'd like to tell me your name, feel free!\",\n", + " \"additional_kwargs\": {},\n", + " \"response_metadata\": {\n", + " \"tokenUsage\": {\n", + " \"completionTokens\": 39,\n", + " \"promptTokens\": 11,\n", + " \"totalTokens\": 50\n", " },\n", - " response_metadata: {}\n", - " },\n", - " lc_namespace: [ \u001b[32m\"langchain_core\"\u001b[39m, \u001b[32m\"messages\"\u001b[39m ],\n", - " content: \u001b[32m\"I'm afraid I don't actually know your name. I'm Claude, an AI assistant created by Anthropic.\"\u001b[39m,\n", - " name: \u001b[90mundefined\u001b[39m,\n", - " additional_kwargs: {\n", - " id: \u001b[32m\"msg_01TNDCwsU7ruVoqJwjKqNrzJ\"\u001b[39m,\n", - " type: \u001b[32m\"message\"\u001b[39m,\n", - " role: \u001b[32m\"assistant\"\u001b[39m,\n", - " model: \u001b[32m\"claude-3-sonnet-20240229\"\u001b[39m,\n", - " stop_sequence: \u001b[1mnull\u001b[22m,\n", - " usage: { input_tokens: \u001b[33m12\u001b[39m, output_tokens: \u001b[33m27\u001b[39m },\n", - " stop_reason: \u001b[32m\"end_turn\"\u001b[39m\n", + " \"finish_reason\": \"stop\"\n", " },\n", - " response_metadata: {\n", - " id: \u001b[32m\"msg_01TNDCwsU7ruVoqJwjKqNrzJ\"\u001b[39m,\n", - " model: \u001b[32m\"claude-3-sonnet-20240229\"\u001b[39m,\n", - " stop_sequence: \u001b[1mnull\u001b[22m,\n", - " usage: { input_tokens: \u001b[33m12\u001b[39m, output_tokens: \u001b[33m27\u001b[39m },\n", - " stop_reason: \u001b[32m\"end_turn\"\u001b[39m\n", - " },\n", - " tool_calls: [],\n", - " invalid_tool_calls: []\n", + " \"tool_calls\": [],\n", + " \"invalid_tool_calls\": [],\n", + " \"usage_metadata\": {\n", + " \"input_tokens\": 11,\n", + " \"output_tokens\": 39,\n", + " \"total_tokens\": 50\n", + " }\n", "}" ] }, - "execution_count": 2, + "execution_count": 3, "metadata": {}, "output_type": "execute_result" } @@ -233,54 +211,35 @@ }, { "cell_type": "code", - "execution_count": 3, + "execution_count": 4, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "AIMessage {\n", - " lc_serializable: \u001b[33mtrue\u001b[39m,\n", - " lc_kwargs: {\n", - " content: \u001b[32m\"You said your name is Bob.\"\u001b[39m,\n", - " tool_calls: [],\n", - " invalid_tool_calls: [],\n", - " additional_kwargs: {\n", - " id: \u001b[32m\"msg_01AEQMme3Z1MFKHW8PeDBJ7g\"\u001b[39m,\n", - " type: \u001b[32m\"message\"\u001b[39m,\n", - " role: \u001b[32m\"assistant\"\u001b[39m,\n", - " model: \u001b[32m\"claude-3-sonnet-20240229\"\u001b[39m,\n", - " stop_sequence: \u001b[1mnull\u001b[22m,\n", - " usage: { input_tokens: \u001b[33m33\u001b[39m, output_tokens: \u001b[33m10\u001b[39m },\n", - " stop_reason: \u001b[32m\"end_turn\"\u001b[39m\n", + " \"id\": \"chatcmpl-A64ohhg3P4BuIiw8mUCLI3zYHNOvS\",\n", + " \"content\": \"Your name is Bob! How can I help you today, Bob?\",\n", + " \"additional_kwargs\": {},\n", + " \"response_metadata\": {\n", + " \"tokenUsage\": {\n", + " \"completionTokens\": 14,\n", + " \"promptTokens\": 33,\n", + " \"totalTokens\": 47\n", " },\n", - " response_metadata: {}\n", + " \"finish_reason\": \"stop\"\n", " },\n", - " lc_namespace: [ \u001b[32m\"langchain_core\"\u001b[39m, \u001b[32m\"messages\"\u001b[39m ],\n", - " content: \u001b[32m\"You said your name is Bob.\"\u001b[39m,\n", - " name: \u001b[90mundefined\u001b[39m,\n", - " additional_kwargs: {\n", - " id: \u001b[32m\"msg_01AEQMme3Z1MFKHW8PeDBJ7g\"\u001b[39m,\n", - " type: \u001b[32m\"message\"\u001b[39m,\n", - " role: \u001b[32m\"assistant\"\u001b[39m,\n", - " model: \u001b[32m\"claude-3-sonnet-20240229\"\u001b[39m,\n", - " stop_sequence: \u001b[1mnull\u001b[22m,\n", - " usage: { input_tokens: \u001b[33m33\u001b[39m, output_tokens: \u001b[33m10\u001b[39m },\n", - " stop_reason: \u001b[32m\"end_turn\"\u001b[39m\n", - " },\n", - " response_metadata: {\n", - " id: \u001b[32m\"msg_01AEQMme3Z1MFKHW8PeDBJ7g\"\u001b[39m,\n", - " model: \u001b[32m\"claude-3-sonnet-20240229\"\u001b[39m,\n", - " stop_sequence: \u001b[1mnull\u001b[22m,\n", - " usage: { input_tokens: \u001b[33m33\u001b[39m, output_tokens: \u001b[33m10\u001b[39m },\n", - " stop_reason: \u001b[32m\"end_turn\"\u001b[39m\n", - " },\n", - " tool_calls: [],\n", - " invalid_tool_calls: []\n", + " \"tool_calls\": [],\n", + " \"invalid_tool_calls\": [],\n", + " \"usage_metadata\": {\n", + " \"input_tokens\": 33,\n", + " \"output_tokens\": 14,\n", + " \"total_tokens\": 47\n", + " }\n", "}" ] }, - "execution_count": 3, + "execution_count": 4, "metadata": {}, "output_type": "execute_result" } @@ -330,7 +289,7 @@ }, { "cell_type": "code", - "execution_count": 4, + "execution_count": 5, "metadata": {}, "outputs": [], "source": [ @@ -371,16 +330,16 @@ }, { "cell_type": "code", - "execution_count": 5, + "execution_count": 6, "metadata": {}, "outputs": [ { "data": { "text/plain": [ - "\u001b[32m\"Hi Bob, nice to meet you! I'm an AI assistant. I'll remember that your name is Bob as we continue ou\"\u001b[39m... 110 more characters" + "\u001b[32m\"Hi Bob! How can I assist you today?\"\u001b[39m" ] }, - "execution_count": 5, + "execution_count": 6, "metadata": {}, "output_type": "execute_result" } @@ -401,16 +360,16 @@ }, { "cell_type": "code", - "execution_count": 6, + "execution_count": 7, "metadata": {}, "outputs": [ { "data": { "text/plain": [ - "\u001b[32m\"Your name is Bob. You introduced yourself as Bob at the start of our conversation.\"\u001b[39m" + "\u001b[32m\"Your name is Bob. How can I help you today?\"\u001b[39m" ] }, - "execution_count": 6, + "execution_count": 7, "metadata": {}, "output_type": "execute_result" } @@ -432,16 +391,16 @@ }, { "cell_type": "code", - "execution_count": 7, + "execution_count": 8, "metadata": {}, "outputs": [ { "data": { "text/plain": [ - "\u001b[32m\"I'm afraid I don't actually know your name. As an AI assistant without any prior context about you, \"\u001b[39m... 61 more characters" + "\u001b[32m\"I'm sorry, but I don't have your name. If you tell me, I'll remember it for our future conversations\"\u001b[39m... 1 more character" ] }, - "execution_count": 7, + "execution_count": 8, "metadata": {}, "output_type": "execute_result" } @@ -469,16 +428,16 @@ }, { "cell_type": "code", - "execution_count": 8, + "execution_count": 9, "metadata": {}, "outputs": [ { "data": { "text/plain": [ - "\u001b[32m`Your name is Bob. I clearly remember you telling me \"Hi! I'm Bob\" when we started talking.`\u001b[39m" + "\u001b[32m\"Your name is Bob. What would you like to talk about?\"\u001b[39m" ] }, - "execution_count": 8, + "execution_count": 9, "metadata": {}, "output_type": "execute_result" } @@ -519,7 +478,7 @@ }, { "cell_type": "code", - "execution_count": 9, + "execution_count": 10, "metadata": {}, "outputs": [], "source": [ @@ -551,7 +510,7 @@ }, { "cell_type": "code", - "execution_count": 10, + "execution_count": 11, "metadata": {}, "outputs": [], "source": [ @@ -573,16 +532,16 @@ }, { "cell_type": "code", - "execution_count": 11, + "execution_count": 12, "metadata": {}, "outputs": [ { "data": { "text/plain": [ - "\u001b[32m\"I'm afraid I don't actually know your name. You haven't provided that detail to me yet.\"\u001b[39m" + "\u001b[32m\"You haven't shared your name with me yet. What is it?\"\u001b[39m" ] }, - "execution_count": 11, + "execution_count": 12, "metadata": {}, "output_type": "execute_result" } @@ -606,16 +565,16 @@ }, { "cell_type": "code", - "execution_count": 12, + "execution_count": 13, "metadata": {}, "outputs": [ { "data": { "text/plain": [ - "\u001b[32m\"You said earlier that you like vanilla ice cream.\"\u001b[39m" + "\u001b[32m\"Your favorite ice cream is vanilla!\"\u001b[39m" ] }, - "execution_count": 12, + "execution_count": 13, "metadata": {}, "output_type": "execute_result" } @@ -645,7 +604,7 @@ { "data": { "text/plain": [ - "\u001b[32m\"I'm afraid I don't actually know your name since you haven't provided it to me yet. I don't have pe\"\u001b[39m... 66 more characters" + "\u001b[32m\"You haven't shared your name with me yet. What is it?\"\u001b[39m" ] }, "execution_count": 14, @@ -657,7 +616,7 @@ "const messageHistories2: Record = {};\n", "\n", "const withMessageHistory2 = new RunnableWithMessageHistory({\n", - " runnable: chain,\n", + " runnable: chain2,\n", " getMessageHistory: async (sessionId) => {\n", " if (messageHistories2[sessionId] === undefined) {\n", " const messageHistory = new InMemoryChatMessageHistory();\n", @@ -679,6 +638,7 @@ "const response7 = await withMessageHistory2.invoke(\n", " {\n", " input: \"whats my name?\",\n", + " chat_history: [],\n", " },\n", " config4,\n", ")\n", @@ -701,7 +661,7 @@ { "data": { "text/plain": [ - "\u001b[32m\"I'm sorry, I don't have any information about your favorite ice cream flavor since you haven't share\"\u001b[39m... 167 more characters" + "\u001b[32m\"You haven't mentioned your favorite ice cream yet. What is it?\"\u001b[39m" ] }, "execution_count": 15, @@ -711,7 +671,8 @@ ], "source": [ "const response8 = await withMessageHistory2.invoke({\n", - " input: \"whats my favorite ice cream?\"\n", + " input: \"whats my favorite ice cream?\",\n", + " chat_history: [],\n", "}, config4);\n", "\n", "response8.content" @@ -739,7 +700,7 @@ }, { "cell_type": "code", - "execution_count": 18, + "execution_count": 16, "metadata": {}, "outputs": [ { @@ -748,36 +709,38 @@ "text": [ "| \n", "| Hi\n", - "| Tod\n", - "| d!\n", + "| Todd\n", + "| !\n", "| Here\n", - "| 's\n", + "| ’s\n", "| a\n", - "| silly\n", "| joke\n", "| for\n", "| you\n", "| :\n", - "| \n", + "| \n", + "\n", "\n", - "Why\n", - "| di\n", - "| d the\n", - "| tom\n", - "| ato\n", - "| turn\n", - "| re\n", - "| d?\n", - "| Because\n", - "| it\n", - "| saw\n", + "| Why\n", + "| did\n", "| the\n", - "| sal\n", - "| a\n", - "| d \n", - "| dressing\n", + "| scare\n", + "| crow\n", + "| win\n", + "| an\n", + "| award\n", + "| ?\n", + "| \n", + "\n", + "\n", + "| Because\n", + "| he\n", + "| was\n", + "| outstanding\n", + "| in\n", + "| his\n", + "| field\n", "| !\n", - "| \n", "| \n" ] } @@ -791,6 +754,7 @@ "\n", "const stream = await withMessageHistory2.stream({\n", " input: \"hi! I'm todd. tell me a joke\",\n", + " chat_history: [],\n", "}, config5);\n", "\n", "for await (const chunk of stream) {\n", From 5565c02efd25445ae2d2af586e71658c571fce1f Mon Sep 17 00:00:00 2001 From: Jacob Lee Date: Tue, 10 Sep 2024 17:39:41 -0700 Subject: [PATCH 160/344] docs: Update wording in tool choice guide (#6732) --- docs/core_docs/docs/how_to/tool_choice.ipynb | 31 ++++---------------- 1 file changed, 5 insertions(+), 26 deletions(-) diff --git a/docs/core_docs/docs/how_to/tool_choice.ipynb b/docs/core_docs/docs/how_to/tool_choice.ipynb index bb568f25cb06..a60491045a2c 100644 --- a/docs/core_docs/docs/how_to/tool_choice.ipynb +++ b/docs/core_docs/docs/how_to/tool_choice.ipynb @@ -103,8 +103,8 @@ "const llmForcedToMultiply = llm.bindTools(tools, {\n", " tool_choice: \"Multiply\",\n", "})\n", - "const result = await llmForcedToMultiply.invoke(\"what is 2 + 4\");\n", - "console.log(JSON.stringify(result.tool_calls, null, 2));" + "const multiplyResult = await llmForcedToMultiply.invoke(\"what is 2 + 4\");\n", + "console.log(JSON.stringify(multiplyResult.tool_calls, null, 2));" ] }, { @@ -118,7 +118,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "We can also just force our tool to select at least one of our tools by passing in the \"any\" (or \"required\" which is OpenAI specific) keyword to the `tool_choice` parameter." + "We can also just force our tool to select at least one of our tools by passing `\"any\"` (or for OpenAI models, the equivalent, `\"required\"`) to the `tool_choice` parameter." ] }, { @@ -148,29 +148,8 @@ "const llmForcedToUseTool = llm.bindTools(tools, {\n", " tool_choice: \"any\",\n", "})\n", - "const result = await llmForcedToUseTool.invoke(\"What day is today?\");\n", - "console.log(JSON.stringify(result.tool_calls, null, 2));" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "```{=mdx}\n", - "\n", - ":::note\n", - "\n", - "Currently, the following packages have a minimum version for this style of forced tool calls:\n", - "\n", - "| Package Name | Min Package Version | Min Core Version |\n", - "|---------------------|---------------------|------------------|\n", - "| `@langchain/aws` | `0.0.3` | `0.2.17` |\n", - "| `@langchain/openai` | `0.2.4` | `0.2.17` |\n", - "| `@langchain/groq` | `0.0.14` | `0.2.17` |\n", - "\n", - ":::\n", - "\n", - "```" + "const anyToolResult = await llmForcedToUseTool.invoke(\"What day is today?\");\n", + "console.log(JSON.stringify(anyToolResult.tool_calls, null, 2));" ] } ], From da14961298f8a9ab6dfb0f79766b40b47b0b1ac3 Mon Sep 17 00:00:00 2001 From: Allen Firstenberg Date: Wed, 11 Sep 2024 01:22:33 -0400 Subject: [PATCH 161/344] google-common[major]: Media manager (#5835) --- libs/langchain-google-common/.gitignore | 8 + .../langchain.config.js | 2 + libs/langchain-google-common/package.json | 28 +- libs/langchain-google-common/src/auth.ts | 36 +- .../src/chat_models.ts | 87 +- .../langchain-google-common/src/connection.ts | 66 +- .../langchain-google-common/src/embeddings.ts | 7 +- .../src/experimental/media.ts | 803 +++++++++++ .../src/experimental/utils/media_core.ts | 669 +++++++++ libs/langchain-google-common/src/llms.ts | 26 +- .../src/tests/chat_models.test.ts | 207 ++- .../src/tests/utils.test.ts | 482 ++++++- libs/langchain-google-common/src/types.ts | 13 +- .../src/utils/gemini.ts | 1202 +++++++++-------- libs/langchain-google-gauth/src/index.ts | 2 + libs/langchain-google-gauth/src/media.ts | 31 + .../src/tests/chat_models.int.test.ts | 315 +++++ .../src/tests/data/blue-square.png | Bin 0 -> 176 bytes .../src/tests/media.int.test.ts | 137 ++ .../src/tests/chat_models.int.test.ts | 83 ++ libs/langchain-google-webauth/package.json | 3 +- libs/langchain-google-webauth/src/media.ts | 33 + .../src/tests/chat_models.int.test.ts | 247 ++++ .../src/tests/data/blue-square.png | Bin 0 -> 176 bytes .../src/tests/data/rainbow.mp4 | Bin 0 -> 1020253 bytes .../src/tests/media.int.test.ts | 201 +++ yarn.lock | 1 + 27 files changed, 3949 insertions(+), 740 deletions(-) create mode 100644 libs/langchain-google-common/src/experimental/media.ts create mode 100644 libs/langchain-google-common/src/experimental/utils/media_core.ts create mode 100644 libs/langchain-google-gauth/src/media.ts create mode 100644 libs/langchain-google-gauth/src/tests/chat_models.int.test.ts create mode 100644 libs/langchain-google-gauth/src/tests/data/blue-square.png create mode 100644 libs/langchain-google-gauth/src/tests/media.int.test.ts create mode 100644 libs/langchain-google-webauth/src/media.ts create mode 100644 libs/langchain-google-webauth/src/tests/chat_models.int.test.ts create mode 100644 libs/langchain-google-webauth/src/tests/data/blue-square.png create mode 100644 libs/langchain-google-webauth/src/tests/data/rainbow.mp4 create mode 100644 libs/langchain-google-webauth/src/tests/media.int.test.ts diff --git a/libs/langchain-google-common/.gitignore b/libs/langchain-google-common/.gitignore index df014a2d426b..c4537f159680 100644 --- a/libs/langchain-google-common/.gitignore +++ b/libs/langchain-google-common/.gitignore @@ -10,6 +10,14 @@ types.cjs types.js types.d.ts types.d.cts +experimental/media.cjs +experimental/media.js +experimental/media.d.ts +experimental/media.d.cts +experimental/utils/media_core.cjs +experimental/utils/media_core.js +experimental/utils/media_core.d.ts +experimental/utils/media_core.d.cts node_modules dist .yarn diff --git a/libs/langchain-google-common/langchain.config.js b/libs/langchain-google-common/langchain.config.js index df02f88bd793..458e4abdc1a4 100644 --- a/libs/langchain-google-common/langchain.config.js +++ b/libs/langchain-google-common/langchain.config.js @@ -16,6 +16,8 @@ export const config = { index: "index", utils: "utils/index", types: "types", + "experimental/media": "experimental/media", + "experimental/utils/media_core": "experimental/utils/media_core", }, tsConfigPath: resolve("./tsconfig.json"), cjsSource: "./dist-cjs", diff --git a/libs/langchain-google-common/package.json b/libs/langchain-google-common/package.json index 242093637b64..2bafc1cbcc5e 100644 --- a/libs/langchain-google-common/package.json +++ b/libs/langchain-google-common/package.json @@ -92,6 +92,24 @@ "import": "./types.js", "require": "./types.cjs" }, + "./experimental/media": { + "types": { + "import": "./experimental/media.d.ts", + "require": "./experimental/media.d.cts", + "default": "./experimental/media.d.ts" + }, + "import": "./experimental/media.js", + "require": "./experimental/media.cjs" + }, + "./experimental/utils/media_core": { + "types": { + "import": "./experimental/utils/media_core.d.ts", + "require": "./experimental/utils/media_core.d.cts", + "default": "./experimental/utils/media_core.d.ts" + }, + "import": "./experimental/utils/media_core.js", + "require": "./experimental/utils/media_core.cjs" + }, "./package.json": "./package.json" }, "files": [ @@ -107,6 +125,14 @@ "types.cjs", "types.js", "types.d.ts", - "types.d.cts" + "types.d.cts", + "experimental/media.cjs", + "experimental/media.js", + "experimental/media.d.ts", + "experimental/media.d.cts", + "experimental/utils/media_core.cjs", + "experimental/utils/media_core.js", + "experimental/utils/media_core.d.ts", + "experimental/utils/media_core.d.cts" ] } diff --git a/libs/langchain-google-common/src/auth.ts b/libs/langchain-google-common/src/auth.ts index 9e278a9605d2..60e0fab4f998 100644 --- a/libs/langchain-google-common/src/auth.ts +++ b/libs/langchain-google-common/src/auth.ts @@ -1,9 +1,9 @@ import { ReadableJsonStream } from "./utils/stream.js"; import { GooglePlatformType } from "./types.js"; -export type GoogleAbstractedClientOpsMethod = "GET" | "POST"; +export type GoogleAbstractedClientOpsMethod = "GET" | "POST" | "DELETE"; -export type GoogleAbstractedClientOpsResponseType = "json" | "stream"; +export type GoogleAbstractedClientOpsResponseType = "json" | "stream" | "blob"; export type GoogleAbstractedClientOps = { url?: string; @@ -28,6 +28,17 @@ export abstract class GoogleAbstractedFetchClient abstract request(opts: GoogleAbstractedClientOps): unknown; + async _buildData(res: Response, opts: GoogleAbstractedClientOps) { + switch (opts.responseType) { + case "json": + return res.json(); + case "stream": + return new ReadableJsonStream(res.body); + default: + return res.blob(); + } + } + async _request( url: string | undefined, opts: GoogleAbstractedClientOps, @@ -47,7 +58,11 @@ export abstract class GoogleAbstractedFetchClient }, }; if (opts.data !== undefined) { - fetchOptions.body = JSON.stringify(opts.data); + if (typeof opts.data === "string") { + fetchOptions.body = opts.data; + } else { + fetchOptions.body = JSON.stringify(opts.data); + } } const res = await fetch(url, fetchOptions); @@ -57,16 +72,21 @@ export abstract class GoogleAbstractedFetchClient const error = new Error( `Google request failed with status code ${res.status}: ${resText}` ); - // eslint-disable-next-line @typescript-eslint/no-explicit-any + /* eslint-disable @typescript-eslint/no-explicit-any */ (error as any).response = res; + (error as any).details = { + url, + opts, + fetchOptions, + result: res, + }; + /* eslint-enable @typescript-eslint/no-explicit-any */ throw error; } + const data = await this._buildData(res, opts); return { - data: - opts.responseType === "json" - ? await res.json() - : new ReadableJsonStream(res.body), + data, config: {}, status: res.status, statusText: res.statusText, diff --git a/libs/langchain-google-common/src/chat_models.ts b/libs/langchain-google-common/src/chat_models.ts index 2fa539657ec0..4ee4e0f6ed05 100644 --- a/libs/langchain-google-common/src/chat_models.ts +++ b/libs/langchain-google-common/src/chat_models.ts @@ -39,12 +39,7 @@ import { copyAndValidateModelParamsInto, } from "./utils/common.js"; import { AbstractGoogleLLMConnection } from "./connection.js"; -import { - baseMessageToContent, - safeResponseToChatGeneration, - safeResponseToChatResult, - DefaultGeminiSafetyHandler, -} from "./utils/gemini.js"; +import { DefaultGeminiSafetyHandler } from "./utils/gemini.js"; import { ApiKeyGoogleAuth, GoogleAbstractedClient } from "./auth.js"; import { JsonStream } from "./utils/stream.js"; import { ensureParams } from "./utils/failed_handler.js"; @@ -55,6 +50,7 @@ import type { GeminiFunctionDeclaration, GeminiFunctionSchema, GoogleAIToolType, + GeminiAPIConfig, } from "./types.js"; import { zodToGeminiParameters } from "./utils/zod_to_gemini_parameters.js"; @@ -100,61 +96,69 @@ class ChatConnection extends AbstractGoogleLLMConnection< return true; } - formatContents( + async formatContents( input: BaseMessage[], _parameters: GoogleAIModelParams - ): GeminiContent[] { - return input - .map((msg, i) => - baseMessageToContent(msg, input[i - 1], this.useSystemInstruction) + ): Promise { + const inputPromises: Promise[] = input.map((msg, i) => + this.api.baseMessageToContent( + msg, + input[i - 1], + this.useSystemInstruction ) - .reduce((acc, cur) => { - // Filter out the system content - if (cur.every((content) => content.role === "system")) { - return acc; - } - - // Combine adjacent function messages - if ( - cur[0]?.role === "function" && - acc.length > 0 && - acc[acc.length - 1].role === "function" - ) { - acc[acc.length - 1].parts = [ - ...acc[acc.length - 1].parts, - ...cur[0].parts, - ]; - } else { - acc.push(...cur); - } + ); + const inputs = await Promise.all(inputPromises); + return inputs.reduce((acc, cur) => { + // Filter out the system content + if (cur.every((content) => content.role === "system")) { return acc; - }, [] as GeminiContent[]); + } + + // Combine adjacent function messages + if ( + cur[0]?.role === "function" && + acc.length > 0 && + acc[acc.length - 1].role === "function" + ) { + acc[acc.length - 1].parts = [ + ...acc[acc.length - 1].parts, + ...cur[0].parts, + ]; + } else { + acc.push(...cur); + } + + return acc; + }, [] as GeminiContent[]); } - formatSystemInstruction( + async formatSystemInstruction( input: BaseMessage[], _parameters: GoogleAIModelParams - ): GeminiContent { + ): Promise { if (!this.useSystemInstruction) { return {} as GeminiContent; } let ret = {} as GeminiContent; - input.forEach((message, index) => { + for (let index = 0; index < input.length; index += 1) { + const message = input[index]; if (message._getType() === "system") { // For system types, we only want it if it is the first message, // if it appears anywhere else, it should be an error. if (index === 0) { // eslint-disable-next-line prefer-destructuring - ret = baseMessageToContent(message, undefined, true)[0]; + ret = ( + await this.api.baseMessageToContent(message, undefined, true) + )[0]; } else { throw new Error( "System messages are only permitted as the first passed message." ); } } - }); + } return ret; } @@ -168,6 +172,7 @@ export interface ChatGoogleBaseInput GoogleConnectionParams, GoogleAIModelParams, GoogleAISafetyParams, + GeminiAPIConfig, Pick {} /** @@ -338,7 +343,10 @@ export abstract class ChatGoogleBase parameters, options ); - const ret = safeResponseToChatResult(response, this.safetyHandler); + const ret = this.connection.api.safeResponseToChatResult( + response, + this.safetyHandler + ); await runManager?.handleLLMNewToken(ret.generations[0].text); return ret; } @@ -378,7 +386,10 @@ export abstract class ChatGoogleBase } const chunk = output !== null - ? safeResponseToChatGeneration({ data: output }, this.safetyHandler) + ? this.connection.api.safeResponseToChatGeneration( + { data: output }, + this.safetyHandler + ) : new ChatGenerationChunk({ text: "", generationInfo: { finishReason: "stop" }, diff --git a/libs/langchain-google-common/src/connection.ts b/libs/langchain-google-common/src/connection.ts index 4a90e9f795af..7e7da9daa304 100644 --- a/libs/langchain-google-common/src/connection.ts +++ b/libs/langchain-google-common/src/connection.ts @@ -20,6 +20,7 @@ import type { GeminiTool, GeminiFunctionDeclaration, GoogleAIModelRequestParams, + GoogleRawResponse, GoogleAIToolType, } from "./types.js"; import { @@ -28,6 +29,7 @@ import { GoogleAbstractedClientOpsMethod, } from "./auth.js"; import { zodToGeminiParameters } from "./utils/zod_to_gemini_parameters.js"; +import { getGeminiAPI } from "./utils/index.js"; export abstract class GoogleConnection< CallOptions extends AsyncCallerCallOptions, @@ -84,15 +86,23 @@ export abstract class GoogleConnection< return this.constructor.name; } - async _request( + async additionalHeaders(): Promise> { + return {}; + } + + async _buildOpts( data: unknown | undefined, - options: CallOptions - ): Promise { + _options: CallOptions, + requestHeaders: Record = {} + ): Promise { const url = await this.buildUrl(); const method = this.buildMethod(); const infoHeaders = (await this._clientInfoHeaders()) ?? {}; + const additionalHeaders = (await this.additionalHeaders()) ?? {}; const headers = { ...infoHeaders, + ...additionalHeaders, + ...requestHeaders, }; const opts: GoogleAbstractedClientOps = { @@ -108,7 +118,15 @@ export abstract class GoogleConnection< } else { opts.responseType = "json"; } + return opts; + } + async _request( + data: unknown | undefined, + options: CallOptions, + requestHeaders: Record = {} + ): Promise { + const opts = await this._buildOpts(data, options, requestHeaders); const callResponse = await this.caller.callWithOptions( { signal: options?.signal }, async () => this.client.request(opts) @@ -165,6 +183,21 @@ export abstract class GoogleHostConnection< } } +export abstract class GoogleRawConnection< + CallOptions extends AsyncCallerCallOptions, + AuthOptions +> extends GoogleHostConnection { + async _buildOpts( + data: unknown | undefined, + _options: CallOptions, + requestHeaders: Record = {} + ): Promise { + const opts = await super._buildOpts(data, _options, requestHeaders); + opts.responseType = "blob"; + return opts; + } +} + export abstract class GoogleAIConnection< CallOptions extends AsyncCallerCallOptions, InputType, @@ -180,6 +213,9 @@ export abstract class GoogleAIConnection< client: GoogleAbstractedClient; + // eslint-disable-next-line @typescript-eslint/no-explicit-any + api: any; // FIXME: Make this a real type + constructor( fields: GoogleAIBaseLLMInput | undefined, caller: AsyncCaller, @@ -190,6 +226,7 @@ export abstract class GoogleAIConnection< this.client = client; this.modelName = fields?.model ?? fields?.modelName ?? this.model; this.model = this.modelName; + this.api = getGeminiAPI(fields); } get modelFamily(): GoogleLLMModelFamily { @@ -235,14 +272,14 @@ export abstract class GoogleAIConnection< abstract formatData( input: InputType, parameters: GoogleAIModelRequestParams - ): unknown; + ): Promise; async request( input: InputType, parameters: GoogleAIModelRequestParams, options: CallOptions - ): Promise { - const data = this.formatData(input, parameters); + ): Promise { + const data = await this.formatData(input, parameters); const response = await this._request(data, options); return response; } @@ -273,7 +310,7 @@ export abstract class AbstractGoogleLLMConnection< abstract formatContents( input: MessageType, parameters: GoogleAIModelRequestParams - ): GeminiContent[]; + ): Promise; formatGenerationConfig( _input: MessageType, @@ -296,10 +333,10 @@ export abstract class AbstractGoogleLLMConnection< return parameters.safetySettings ?? []; } - formatSystemInstruction( + async formatSystemInstruction( _input: MessageType, _parameters: GoogleAIModelRequestParams - ): GeminiContent { + ): Promise { return {} as GeminiContent; } @@ -362,16 +399,19 @@ export abstract class AbstractGoogleLLMConnection< }; } - formatData( + async formatData( input: MessageType, parameters: GoogleAIModelRequestParams - ): GeminiRequest { - const contents = this.formatContents(input, parameters); + ): Promise { + const contents = await this.formatContents(input, parameters); const generationConfig = this.formatGenerationConfig(input, parameters); const tools = this.formatTools(input, parameters); const toolConfig = this.formatToolConfig(parameters); const safetySettings = this.formatSafetySettings(input, parameters); - const systemInstruction = this.formatSystemInstruction(input, parameters); + const systemInstruction = await this.formatSystemInstruction( + input, + parameters + ); const ret: GeminiRequest = { contents, diff --git a/libs/langchain-google-common/src/embeddings.ts b/libs/langchain-google-common/src/embeddings.ts index 4bf568959efe..d1e2549c631a 100644 --- a/libs/langchain-google-common/src/embeddings.ts +++ b/libs/langchain-google-common/src/embeddings.ts @@ -38,10 +38,10 @@ class EmbeddingsConnection< return "predict"; } - formatData( + async formatData( input: GoogleEmbeddingsInstance[], parameters: GoogleAIModelRequestParams - ): unknown { + ): Promise { return { instances: input, parameters, @@ -172,7 +172,8 @@ export abstract class BaseGoogleEmbeddings ?.map( (response) => response?.data?.predictions?.map( - (result) => result.embeddings.values + // eslint-disable-next-line @typescript-eslint/no-explicit-any + (result: any) => result.embeddings?.values ) ?? [] ) .flat() ?? []; diff --git a/libs/langchain-google-common/src/experimental/media.ts b/libs/langchain-google-common/src/experimental/media.ts new file mode 100644 index 000000000000..89646482ca70 --- /dev/null +++ b/libs/langchain-google-common/src/experimental/media.ts @@ -0,0 +1,803 @@ +import { + AsyncCaller, + AsyncCallerCallOptions, + AsyncCallerParams, +} from "@langchain/core/utils/async_caller"; +import { getEnvironmentVariable } from "@langchain/core/utils/env"; +import { + MediaBlob, + BlobStore, + BlobStoreOptions, + MediaBlobData, +} from "./utils/media_core.js"; +import { + GoogleConnectionParams, + GoogleRawResponse, + GoogleResponse, +} from "../types.js"; +import { GoogleHostConnection, GoogleRawConnection } from "../connection.js"; +import { + ApiKeyGoogleAuth, + GoogleAbstractedClient, + GoogleAbstractedClientOpsMethod, +} from "../auth.js"; + +export interface GoogleUploadConnectionParams + extends GoogleConnectionParams {} + +export abstract class GoogleMultipartUploadConnection< + CallOptions extends AsyncCallerCallOptions, + ResponseType extends GoogleResponse, + AuthOptions +> extends GoogleHostConnection { + constructor( + fields: GoogleConnectionParams | undefined, + caller: AsyncCaller, + client: GoogleAbstractedClient + ) { + super(fields, caller, client); + } + + async _body( + separator: string, + data: MediaBlob, + metadata: Record + ): Promise { + const contentType = data.mimetype; + const { encoded, encoding } = await data.encode(); + const body = [ + `--${separator}`, + "Content-Type: application/json; charset=UTF-8", + "", + JSON.stringify(metadata), + "", + `--${separator}`, + `Content-Type: ${contentType}`, + `Content-Transfer-Encoding: ${encoding}`, + "", + encoded, + `--${separator}--`, + ]; + return body.join("\n"); + } + + async request( + data: MediaBlob, + metadata: Record, + options: CallOptions + ): Promise { + const separator = `separator-${Date.now()}`; + const body = await this._body(separator, data, metadata); + const requestHeaders = { + "Content-Type": `multipart/related; boundary=${separator}`, + "X-Goog-Upload-Protocol": "multipart", + }; + const response = this._request(body, options, requestHeaders); + return response; + } +} + +export abstract class GoogleDownloadConnection< + CallOptions extends AsyncCallerCallOptions, + ResponseType extends GoogleResponse, + AuthOptions +> extends GoogleHostConnection { + async request(options: CallOptions): Promise { + return this._request(undefined, options); + } +} + +export abstract class GoogleDownloadRawConnection< + CallOptions extends AsyncCallerCallOptions, + AuthOptions +> extends GoogleRawConnection { + buildMethod(): GoogleAbstractedClientOpsMethod { + return "GET"; + } + + async request(options: CallOptions): Promise { + return this._request(undefined, options); + } +} + +export interface BlobStoreGoogleParams + extends GoogleConnectionParams, + AsyncCallerParams, + BlobStoreOptions {} + +export abstract class BlobStoreGoogle< + ResponseType extends GoogleResponse, + AuthOptions +> extends BlobStore { + caller: AsyncCaller; + + client: GoogleAbstractedClient; + + constructor(fields?: BlobStoreGoogleParams) { + super(fields); + this.caller = new AsyncCaller(fields ?? {}); + this.client = this.buildClient(fields); + } + + abstract buildClient( + fields?: BlobStoreGoogleParams + ): GoogleAbstractedClient; + + abstract buildSetMetadata([key, blob]: [string, MediaBlob]): Record< + string, + unknown + >; + + abstract buildSetConnection([key, blob]: [ + string, + MediaBlob + ]): GoogleMultipartUploadConnection< + AsyncCallerCallOptions, + ResponseType, + AuthOptions + >; + + async _set(keyValuePair: [string, MediaBlob]): Promise { + const [, blob] = keyValuePair; + const setMetadata = this.buildSetMetadata(keyValuePair); + const metadata = setMetadata; + const options = {}; + const connection = this.buildSetConnection(keyValuePair); + const response = await connection.request(blob, metadata, options); + return response; + } + + async mset(keyValuePairs: [string, MediaBlob][]): Promise { + const ret = keyValuePairs.map((keyValue) => this._set(keyValue)); + await Promise.all(ret); + } + + abstract buildGetMetadataConnection( + key: string + ): GoogleDownloadConnection< + AsyncCallerCallOptions, + ResponseType, + AuthOptions + >; + + async _getMetadata(key: string): Promise> { + const connection = this.buildGetMetadataConnection(key); + const options = {}; + const response = await connection.request(options); + return response.data; + } + + abstract buildGetDataConnection( + key: string + ): GoogleDownloadRawConnection; + + async _getData(key: string): Promise { + const connection = this.buildGetDataConnection(key); + const options = {}; + const response = await connection.request(options); + return response.data; + } + + _getMimetypeFromMetadata(metadata: Record): string { + return metadata.contentType as string; + } + + async _get(key: string): Promise { + const metadata = await this._getMetadata(key); + const data = await this._getData(key); + if (data && metadata) { + const ret = await MediaBlob.fromBlob(data, { metadata, path: key }); + return ret; + } else { + return undefined; + } + } + + async mget(keys: string[]): Promise<(MediaBlob | undefined)[]> { + const ret = keys.map((key) => this._get(key)); + return await Promise.all(ret); + } + + abstract buildDeleteConnection( + key: string + ): GoogleDownloadConnection< + AsyncCallerCallOptions, + GoogleResponse, + AuthOptions + >; + + async _del(key: string): Promise { + const connection = this.buildDeleteConnection(key); + const options = {}; + await connection.request(options); + } + + async mdelete(keys: string[]): Promise { + const ret = keys.map((key) => this._del(key)); + await Promise.all(ret); + } + + // eslint-disable-next-line require-yield + async *yieldKeys(_prefix: string | undefined): AsyncGenerator { + // TODO: Implement. Most have an implementation that uses nextToken. + throw new Error("yieldKeys is not implemented"); + } +} + +/** + * Based on https://cloud.google.com/storage/docs/json_api/v1/objects#resource + */ +export interface GoogleCloudStorageObject extends Record { + id?: string; + name?: string; + contentType?: string; + metadata?: Record; + // This is incomplete. +} + +export interface GoogleCloudStorageResponse extends GoogleResponse { + data: GoogleCloudStorageObject; +} + +export type BucketAndPath = { + bucket: string; + path: string; +}; + +export class GoogleCloudStorageUri { + static uriRegexp = /gs:\/\/([a-z0-9][a-z0-9._-]+[a-z0-9])\/(.*)/; + + bucket: string; + + path: string; + + constructor(uri: string) { + const bucketAndPath = GoogleCloudStorageUri.uriToBucketAndPath(uri); + this.bucket = bucketAndPath.bucket; + this.path = bucketAndPath.path; + } + + get uri() { + return `gs://${this.bucket}/${this.path}`; + } + + get isValid() { + return ( + typeof this.bucket !== "undefined" && typeof this.path !== "undefined" + ); + } + + static uriToBucketAndPath(uri: string): BucketAndPath { + const match = this.uriRegexp.exec(uri); + if (!match) { + throw new Error(`Invalid gs:// URI: ${uri}`); + } + return { + bucket: match[1], + path: match[2], + }; + } + + static isValidUri(uri: string): boolean { + return this.uriRegexp.test(uri); + } +} + +export interface GoogleCloudStorageConnectionParams { + uri: string; +} + +export interface GoogleCloudStorageUploadConnectionParams + extends GoogleUploadConnectionParams, + GoogleCloudStorageConnectionParams {} + +export class GoogleCloudStorageUploadConnection< + AuthOptions +> extends GoogleMultipartUploadConnection< + AsyncCallerCallOptions, + GoogleCloudStorageResponse, + AuthOptions +> { + uri: GoogleCloudStorageUri; + + constructor( + fields: GoogleCloudStorageUploadConnectionParams, + caller: AsyncCaller, + client: GoogleAbstractedClient + ) { + super(fields, caller, client); + this.uri = new GoogleCloudStorageUri(fields.uri); + } + + async buildUrl(): Promise { + return `https://storage.googleapis.com/upload/storage/${this.apiVersion}/b/${this.uri.bucket}/o?uploadType=multipart`; + } +} + +export interface GoogleCloudStorageDownloadConnectionParams + extends GoogleCloudStorageConnectionParams, + GoogleConnectionParams { + method: GoogleAbstractedClientOpsMethod; + alt: "media" | undefined; +} + +export class GoogleCloudStorageDownloadConnection< + ResponseType extends GoogleResponse, + AuthOptions +> extends GoogleDownloadConnection< + AsyncCallerCallOptions, + ResponseType, + AuthOptions +> { + uri: GoogleCloudStorageUri; + + method: GoogleAbstractedClientOpsMethod; + + alt: "media" | undefined; + + constructor( + fields: GoogleCloudStorageDownloadConnectionParams, + caller: AsyncCaller, + client: GoogleAbstractedClient + ) { + super(fields, caller, client); + this.uri = new GoogleCloudStorageUri(fields.uri); + this.method = fields.method; + this.alt = fields.alt; + } + + buildMethod(): GoogleAbstractedClientOpsMethod { + return this.method; + } + + async buildUrl(): Promise { + const path = encodeURIComponent(this.uri.path); + const ret = `https://storage.googleapis.com/storage/${this.apiVersion}/b/${this.uri.bucket}/o/${path}`; + return this.alt ? `${ret}?alt=${this.alt}` : ret; + } +} + +export interface GoogleCloudStorageRawConnectionParams + extends GoogleCloudStorageConnectionParams, + GoogleConnectionParams {} + +export class GoogleCloudStorageRawConnection< + AuthOptions +> extends GoogleDownloadRawConnection { + uri: GoogleCloudStorageUri; + + constructor( + fields: GoogleCloudStorageRawConnectionParams, + caller: AsyncCaller, + client: GoogleAbstractedClient + ) { + super(fields, caller, client); + this.uri = new GoogleCloudStorageUri(fields.uri); + } + + async buildUrl(): Promise { + const path = encodeURIComponent(this.uri.path); + const ret = `https://storage.googleapis.com/storage/${this.apiVersion}/b/${this.uri.bucket}/o/${path}?alt=media`; + return ret; + } +} + +export interface BlobStoreGoogleCloudStorageBaseParams + extends BlobStoreGoogleParams { + uriPrefix: GoogleCloudStorageUri; +} + +export abstract class BlobStoreGoogleCloudStorageBase< + AuthOptions +> extends BlobStoreGoogle { + params: BlobStoreGoogleCloudStorageBaseParams; + + constructor(fields: BlobStoreGoogleCloudStorageBaseParams) { + super(fields); + this.params = fields; + this.defaultStoreOptions = { + ...this.defaultStoreOptions, + pathPrefix: fields.uriPrefix.uri, + }; + } + + buildSetConnection([key, _blob]: [ + string, + MediaBlob + ]): GoogleMultipartUploadConnection< + AsyncCallerCallOptions, + GoogleCloudStorageResponse, + AuthOptions + > { + const params: GoogleCloudStorageUploadConnectionParams = { + ...this.params, + uri: key, + }; + return new GoogleCloudStorageUploadConnection( + params, + this.caller, + this.client + ); + } + + buildSetMetadata([key, blob]: [string, MediaBlob]): Record { + const uri = new GoogleCloudStorageUri(key); + const ret: GoogleCloudStorageObject = { + name: uri.path, + metadata: blob.metadata, + contentType: blob.mimetype, + }; + return ret; + } + + buildGetMetadataConnection( + key: string + ): GoogleDownloadConnection< + AsyncCallerCallOptions, + GoogleCloudStorageResponse, + AuthOptions + > { + const params: GoogleCloudStorageDownloadConnectionParams = { + uri: key, + method: "GET", + alt: undefined, + }; + return new GoogleCloudStorageDownloadConnection< + GoogleCloudStorageResponse, + AuthOptions + >(params, this.caller, this.client); + } + + buildGetDataConnection( + key: string + ): GoogleDownloadRawConnection { + const params: GoogleCloudStorageRawConnectionParams = { + uri: key, + }; + return new GoogleCloudStorageRawConnection( + params, + this.caller, + this.client + ); + } + + buildDeleteConnection( + key: string + ): GoogleDownloadConnection< + AsyncCallerCallOptions, + GoogleResponse, + AuthOptions + > { + const params: GoogleCloudStorageDownloadConnectionParams = { + uri: key, + method: "DELETE", + alt: undefined, + }; + return new GoogleCloudStorageDownloadConnection< + GoogleResponse, + AuthOptions + >(params, this.caller, this.client); + } +} + +export type AIStudioFileState = + | "PROCESSING" + | "ACTIVE" + | "FAILED" + | "STATE_UNSPECIFIED"; + +export type AIStudioFileVideoMetadata = { + videoMetadata: { + videoDuration: string; // Duration in seconds, possibly with fractional, ending in "s" + }; +}; + +export type AIStudioFileMetadata = AIStudioFileVideoMetadata; + +export interface AIStudioFileObject { + name?: string; + displayName?: string; + mimeType?: string; + sizeBytes?: string; // int64 format + createTime?: string; // timestamp format + updateTime?: string; // timestamp format + expirationTime?: string; // timestamp format + sha256Hash?: string; // base64 encoded + uri?: string; + state?: AIStudioFileState; + error?: { + code: number; + message: string; + details: Record[]; + }; + metadata?: AIStudioFileMetadata; +} + +export class AIStudioMediaBlob extends MediaBlob { + _valueAsDate(value: string): Date { + if (!value) { + return new Date(0); + } + return new Date(value); + } + + _metadataFieldAsDate(field: string): Date { + return this._valueAsDate(this.metadata?.[field]); + } + + get createDate(): Date { + return this._metadataFieldAsDate("createTime"); + } + + get updateDate(): Date { + return this._metadataFieldAsDate("updateTime"); + } + + get expirationDate(): Date { + return this._metadataFieldAsDate("expirationTime"); + } + + get isExpired(): boolean { + const now = new Date().toISOString(); + const exp = this.metadata?.expirationTime ?? now; + return exp <= now; + } +} + +export interface AIStudioFileGetResponse extends GoogleResponse { + data: AIStudioFileObject; +} + +export interface AIStudioFileSaveResponse extends GoogleResponse { + data: { + file: AIStudioFileObject; + }; +} + +export interface AIStudioFileListResponse extends GoogleResponse { + data: { + files: AIStudioFileObject[]; + nextPageToken: string; + }; +} + +export type AIStudioFileResponse = + | AIStudioFileGetResponse + | AIStudioFileSaveResponse + | AIStudioFileListResponse; + +export interface AIStudioFileConnectionParams {} + +export interface AIStudioFileUploadConnectionParams + extends GoogleUploadConnectionParams, + AIStudioFileConnectionParams {} + +export class AIStudioFileUploadConnection< + AuthOptions +> extends GoogleMultipartUploadConnection< + AsyncCallerCallOptions, + AIStudioFileSaveResponse, + AuthOptions +> { + apiVersion = "v1beta"; + + async buildUrl(): Promise { + return `https://generativelanguage.googleapis.com/upload/${this.apiVersion}/files`; + } +} + +export interface AIStudioFileDownloadConnectionParams + extends AIStudioFileConnectionParams, + GoogleConnectionParams { + method: GoogleAbstractedClientOpsMethod; + name: string; +} + +export class AIStudioFileDownloadConnection< + ResponseType extends GoogleResponse, + AuthOptions +> extends GoogleDownloadConnection< + AsyncCallerCallOptions, + ResponseType, + AuthOptions +> { + method: GoogleAbstractedClientOpsMethod; + + name: string; + + apiVersion = "v1beta"; + + constructor( + fields: AIStudioFileDownloadConnectionParams, + caller: AsyncCaller, + client: GoogleAbstractedClient + ) { + super(fields, caller, client); + this.method = fields.method; + this.name = fields.name; + } + + buildMethod(): GoogleAbstractedClientOpsMethod { + return this.method; + } + + async buildUrl(): Promise { + return `https://generativelanguage.googleapis.com/${this.apiVersion}/files/${this.name}`; + } +} + +export interface BlobStoreAIStudioFileBaseParams + extends BlobStoreGoogleParams { + retryTime?: number; +} + +export abstract class BlobStoreAIStudioFileBase< + AuthOptions +> extends BlobStoreGoogle { + params?: BlobStoreAIStudioFileBaseParams; + + retryTime: number = 1000; + + constructor(fields?: BlobStoreAIStudioFileBaseParams) { + const params: BlobStoreAIStudioFileBaseParams = { + defaultStoreOptions: { + pathPrefix: "https://generativelanguage.googleapis.com/v1beta/files/", + actionIfInvalid: "removePath", + }, + ...fields, + }; + super(params); + this.params = params; + this.retryTime = params?.retryTime ?? this.retryTime ?? 1000; + } + + _pathToName(path: string): string { + return path.split("/").pop() ?? path; + } + + abstract buildAbstractedClient( + fields?: BlobStoreGoogleParams + ): GoogleAbstractedClient; + + buildApiKeyClient(apiKey: string): GoogleAbstractedClient { + return new ApiKeyGoogleAuth(apiKey); + } + + buildApiKey(fields?: BlobStoreGoogleParams): string | undefined { + return fields?.apiKey ?? getEnvironmentVariable("GOOGLE_API_KEY"); + } + + buildClient( + fields?: BlobStoreGoogleParams + ): GoogleAbstractedClient { + const apiKey = this.buildApiKey(fields); + if (apiKey) { + return this.buildApiKeyClient(apiKey); + } else { + // TODO: Test that you can use OAuth to access + return this.buildAbstractedClient(fields); + } + } + + async _regetMetadata(key: string): Promise { + // Sleep for some time period + // eslint-disable-next-line no-promise-executor-return + await new Promise((resolve) => setTimeout(resolve, this.retryTime)); + + // Fetch the latest metadata + return this._getMetadata(key); + } + + async _set([key, blob]: [ + string, + MediaBlob + ]): Promise { + const response = (await super._set([ + key, + blob, + ])) as AIStudioFileSaveResponse; + + let file = response.data?.file ?? { state: "FAILED" }; + while (file.state === "PROCESSING" && file.uri && this.retryTime > 0) { + file = await this._regetMetadata(file.uri); + } + + // The response should contain the name (and valid URI), so we need to + // update the blob with this. We can't return a new blob, since mset() + // doesn't return anything. + /* eslint-disable no-param-reassign */ + blob.path = file.uri; + blob.metadata = { + ...blob.metadata, + ...file, + }; + /* eslint-enable no-param-reassign */ + + return response; + } + + buildSetConnection([_key, _blob]: [ + string, + MediaBlob + ]): GoogleMultipartUploadConnection< + AsyncCallerCallOptions, + AIStudioFileResponse, + AuthOptions + > { + return new AIStudioFileUploadConnection( + this.params, + this.caller, + this.client + ); + } + + buildSetMetadata([_key, _blob]: [string, MediaBlob]): Record< + string, + unknown + > { + return {}; + } + + buildGetMetadataConnection( + key: string + ): GoogleDownloadConnection< + AsyncCallerCallOptions, + AIStudioFileResponse, + AuthOptions + > { + const params: AIStudioFileDownloadConnectionParams = { + ...this.params, + method: "GET", + name: this._pathToName(key), + }; + return new AIStudioFileDownloadConnection< + AIStudioFileResponse, + AuthOptions + >(params, this.caller, this.client); + } + + buildGetDataConnection( + _key: string + ): GoogleDownloadRawConnection { + throw new Error("AI Studio File API does not provide data"); + } + + async _get(key: string): Promise { + const metadata = await this._getMetadata(key); + if (metadata) { + const contentType = + (metadata?.mimeType as string) ?? "application/octet-stream"; + // TODO - Get the actual data (and other metadata) from an optional backing store + const data: MediaBlobData = { + value: "", + type: contentType, + }; + + return new MediaBlob({ + path: key, + data, + metadata, + }); + } else { + return undefined; + } + } + + buildDeleteConnection( + key: string + ): GoogleDownloadConnection< + AsyncCallerCallOptions, + AIStudioFileResponse, + AuthOptions + > { + const params: AIStudioFileDownloadConnectionParams = { + ...this.params, + method: "DELETE", + name: this._pathToName(key), + }; + return new AIStudioFileDownloadConnection< + AIStudioFileResponse, + AuthOptions + >(params, this.caller, this.client); + } +} diff --git a/libs/langchain-google-common/src/experimental/utils/media_core.ts b/libs/langchain-google-common/src/experimental/utils/media_core.ts new file mode 100644 index 000000000000..f27d5c55ed52 --- /dev/null +++ b/libs/langchain-google-common/src/experimental/utils/media_core.ts @@ -0,0 +1,669 @@ +import { v1, v4 } from "uuid"; // FIXME - it is importing the wrong uuid, so v6 and v7 aren't implemented +import { BaseStore } from "@langchain/core/stores"; +import { Serializable } from "@langchain/core/load/serializable"; + +export type MediaBlobData = { + value: string; // In Base64 encoding + type: string; // The mime type and possibly encoding +}; + +export interface MediaBlobParameters { + data?: MediaBlobData; + + metadata?: Record; + + path?: string; +} + +function bytesToString(dataArray: Uint8Array): string { + // Need to handle the array in smaller chunks to deal with stack size limits + let ret = ""; + const chunkSize = 102400; + for (let i = 0; i < dataArray.length; i += chunkSize) { + const chunk = dataArray.subarray(i, i + chunkSize); + ret += String.fromCharCode(...chunk); + } + + return ret; +} + +/** + * Represents a chunk of data that can be identified by the path where the + * data is (or will be) located, along with optional metadata about the data. + */ +export class MediaBlob extends Serializable implements MediaBlobParameters { + lc_serializable = true; + + lc_namespace = [ + "langchain", + "google_common", + "experimental", + "utils", + "media_core", + ]; + + data: MediaBlobData = { + value: "", + type: "text/plain", + }; + + // eslint-disable-next-line @typescript-eslint/no-explicit-any + metadata?: Record; + + path?: string; + + constructor(params: MediaBlobParameters) { + super(params); + + this.data = params.data ?? this.data; + this.metadata = params.metadata; + this.path = params.path; + } + + get size(): number { + return this.asBytes.length; + } + + get dataType(): string { + return this.data?.type ?? ""; + } + + get encoding(): string { + const charsetEquals = this.dataType.indexOf("charset="); + return charsetEquals === -1 + ? "utf-8" + : this.dataType.substring(charsetEquals + 8); + } + + get mimetype(): string { + const semicolon = this.dataType.indexOf(";"); + return semicolon === -1 + ? this.dataType + : this.dataType.substring(0, semicolon); + } + + get asBytes(): Uint8Array { + if (!this.data) { + return Uint8Array.from([]); + } + const binString = atob(this.data?.value); + const ret = new Uint8Array(binString.length); + for (let co = 0; co < binString.length; co += 1) { + ret[co] = binString.charCodeAt(co); + } + return ret; + } + + async asString(): Promise { + return bytesToString(this.asBytes); + } + + async asBase64(): Promise { + return this.data?.value ?? ""; + } + + async asDataUrl(): Promise { + return `data:${this.mimetype};base64,${await this.asBase64()}`; + } + + async asUri(): Promise { + return this.path ?? (await this.asDataUrl()); + } + + async encode(): Promise<{ encoded: string; encoding: string }> { + const dataUrl = await this.asDataUrl(); + const comma = dataUrl.indexOf(","); + const encoded = dataUrl.substring(comma + 1); + const encoding: string = dataUrl.indexOf("base64") > -1 ? "base64" : "8bit"; + return { + encoded, + encoding, + }; + } + + static fromDataUrl(url: string): MediaBlob { + if (!url.startsWith("data:")) { + throw new Error("Not a data: URL"); + } + const colon = url.indexOf(":"); + const semicolon = url.indexOf(";"); + const mimeType = url.substring(colon + 1, semicolon); + + const comma = url.indexOf(","); + const base64Data = url.substring(comma + 1); + + const data: MediaBlobData = { + type: mimeType, + value: base64Data, + }; + + return new MediaBlob({ + data, + path: url, + }); + } + + static async fromBlob( + blob: Blob, + other?: Omit + ): Promise { + const valueBuffer = await blob.arrayBuffer(); + const valueArray = new Uint8Array(valueBuffer); + const valueStr = bytesToString(valueArray); + const value = btoa(valueStr); + + return new MediaBlob({ + ...other, + data: { + value, + type: blob.type, + }, + }); + } +} + +export type ActionIfInvalidAction = + | "ignore" + | "prefixPath" + | "prefixUuid1" + | "prefixUuid4" + | "prefixUuid6" + | "prefixUuid7" + | "removePath"; + +export interface BlobStoreStoreOptions { + /** + * If the path is missing or invalid in the blob, how should we create + * a new path? + * Subclasses may define their own methods, but the following are supported + * by default: + * - Undefined or an emtpy string: Reject the blob + * - "ignore": Attempt to store it anyway (but this may fail) + * - "prefixPath": Use the default prefix for the BlobStore and get the + * unique portion from the URL. The original path is stored in the metadata + * - "prefixUuid": Use the default prefix for the BlobStore and get the + * unique portion from a generated UUID. The original path is stored + * in the metadata + */ + actionIfInvalid?: ActionIfInvalidAction; + + /** + * The expected prefix for URIs that are stored. + * This may be used to test if a MediaBlob is valid and used to create a new + * path if "prefixPath" or "prefixUuid" is set for actionIfInvalid. + */ + pathPrefix?: string; +} + +export type ActionIfBlobMissingAction = "emptyBlob"; + +export interface BlobStoreFetchOptions { + /** + * If the blob is not found when fetching, what should we do? + * Subclasses may define their own methods, but the following are supported + * by default: + * - Undefined or an empty string: return undefined + * - "emptyBlob": return a new MediaBlob that has the path set, but nothing else. + */ + actionIfBlobMissing?: ActionIfBlobMissingAction; +} + +export interface BlobStoreOptions { + defaultStoreOptions?: BlobStoreStoreOptions; + + defaultFetchOptions?: BlobStoreFetchOptions; +} + +/** + * A specialized Store that is designed to handle MediaBlobs and use the + * key that is included in the blob to determine exactly how it is stored. + * + * The full details of a MediaBlob may be changed when it is stored. + * For example, it may get additional or different Metadata. This should be + * what is returned when the store() method is called. + * + * Although BlobStore extends BaseStore, not all of the methods from + * BaseStore may be implemented (or even possible). Those that are not + * implemented should be documented and throw an Error if called. + */ +export abstract class BlobStore extends BaseStore { + lc_namespace = ["langchain", "google-common"]; // FIXME - What should this be? And why? + + defaultStoreOptions: BlobStoreStoreOptions; + + defaultFetchOptions: BlobStoreFetchOptions; + + constructor(opts?: BlobStoreOptions) { + super(opts); + this.defaultStoreOptions = opts?.defaultStoreOptions ?? {}; + this.defaultFetchOptions = opts?.defaultFetchOptions ?? {}; + } + + protected async _realKey(key: string | MediaBlob): Promise { + return typeof key === "string" ? key : await key.asUri(); + } + + /** + * Is the path supported by this BlobStore? + * + * Although this is async, this is expected to be a relatively fast operation + * (ie - you shouldn't make network calls). + * + * @param path The path to check + * @param opts Any options (if needed) that may be used to determine if it is valid + * @return If the path is supported + */ + hasValidPath( + path: string | undefined, + opts?: BlobStoreStoreOptions + ): Promise { + const prefix = opts?.pathPrefix ?? ""; + const isPrefixed = typeof path !== "undefined" && path.startsWith(prefix); + return Promise.resolve(isPrefixed); + } + + protected _blobPathSuffix(blob: MediaBlob): string { + // Get the path currently set and make sure we treat it as a string + const blobPath = `${blob.path}`; + + // Advance past the first set of / + let pathStart = blobPath.indexOf("/") + 1; + while (blobPath.charAt(pathStart) === "/") { + pathStart += 1; + } + + // We will use the rest as the path for a replacement + return blobPath.substring(pathStart); + } + + protected async _newBlob( + oldBlob: MediaBlob, + newPath?: string + ): Promise { + const oldPath = oldBlob.path; + const metadata = oldBlob?.metadata ?? {}; + metadata.langchainOldPath = oldPath; + const newBlob = new MediaBlob({ + ...oldBlob, + metadata, + }); + if (newPath) { + newBlob.path = newPath; + } else if (newBlob.path) { + delete newBlob.path; + } + return newBlob; + } + + protected async _validBlobPrefixPath( + blob: MediaBlob, + opts?: BlobStoreStoreOptions + ): Promise { + const prefix = opts?.pathPrefix ?? ""; + const suffix = this._blobPathSuffix(blob); + const newPath = `${prefix}${suffix}`; + return this._newBlob(blob, newPath); + } + + protected _validBlobPrefixUuidFunction( + name: ActionIfInvalidAction | string + ): string { + switch (name) { + case "prefixUuid1": + return v1(); + case "prefixUuid4": + return v4(); + // case "prefixUuid6": return v6(); + // case "prefixUuid7": return v7(); + default: + throw new Error(`Unknown uuid function: ${name}`); + } + } + + protected async _validBlobPrefixUuid( + blob: MediaBlob, + opts?: BlobStoreStoreOptions + ): Promise { + const prefix = opts?.pathPrefix ?? ""; + const suffix = this._validBlobPrefixUuidFunction( + opts?.actionIfInvalid ?? "prefixUuid4" + ); + const newPath = `${prefix}${suffix}`; + return this._newBlob(blob, newPath); + } + + protected async _validBlobRemovePath( + blob: MediaBlob, + _opts?: BlobStoreStoreOptions + ): Promise { + return this._newBlob(blob, undefined); + } + + /** + * Based on the blob and options, return a blob that has a valid path + * that can be saved. + * @param blob + * @param opts + */ + protected async _validStoreBlob( + blob: MediaBlob, + opts?: BlobStoreStoreOptions + ): Promise { + if (await this.hasValidPath(blob.path, opts)) { + return blob; + } + switch (opts?.actionIfInvalid) { + case "ignore": + return blob; + case "prefixPath": + return this._validBlobPrefixPath(blob, opts); + case "prefixUuid1": + case "prefixUuid4": + case "prefixUuid6": + case "prefixUuid7": + return this._validBlobPrefixUuid(blob, opts); + case "removePath": + return this._validBlobRemovePath(blob, opts); + default: + return undefined; + } + } + + async store( + blob: MediaBlob, + opts: BlobStoreStoreOptions = {} + ): Promise { + const allOpts: BlobStoreStoreOptions = { + ...this.defaultStoreOptions, + ...opts, + }; + const validBlob = await this._validStoreBlob(blob, allOpts); + if (typeof validBlob !== "undefined") { + const validKey = await validBlob.asUri(); + await this.mset([[validKey, validBlob]]); + const savedKey = await validBlob.asUri(); + return await this.fetch(savedKey); + } + return undefined; + } + + protected async _missingFetchBlobEmpty( + path: string, + _opts?: BlobStoreFetchOptions + ): Promise { + return new MediaBlob({ path }); + } + + protected async _missingFetchBlob( + path: string, + opts?: BlobStoreFetchOptions + ): Promise { + switch (opts?.actionIfBlobMissing) { + case "emptyBlob": + return this._missingFetchBlobEmpty(path, opts); + default: + return undefined; + } + } + + async fetch( + key: string | MediaBlob, + opts: BlobStoreFetchOptions = {} + ): Promise { + const allOpts: BlobStoreFetchOptions = { + ...this.defaultFetchOptions, + ...opts, + }; + const realKey = await this._realKey(key); + const ret = await this.mget([realKey]); + return ret?.[0] ?? (await this._missingFetchBlob(realKey, allOpts)); + } +} + +export interface BackedBlobStoreOptions extends BlobStoreOptions { + backingStore: BaseStore; +} + +export class BackedBlobStore extends BlobStore { + backingStore: BaseStore; + + constructor(opts: BackedBlobStoreOptions) { + super(opts); + this.backingStore = opts.backingStore; + } + + mdelete(keys: string[]): Promise { + return this.backingStore.mdelete(keys); + } + + mget(keys: string[]): Promise<(MediaBlob | undefined)[]> { + return this.backingStore.mget(keys); + } + + mset(keyValuePairs: [string, MediaBlob][]): Promise { + return this.backingStore.mset(keyValuePairs); + } + + yieldKeys(prefix: string | undefined): AsyncGenerator { + return this.backingStore.yieldKeys(prefix); + } +} + +export interface ReadThroughBlobStoreOptions extends BlobStoreOptions { + baseStore: BlobStore; + backingStore: BlobStore; +} + +export class ReadThroughBlobStore extends BlobStore { + baseStore: BlobStore; + + backingStore: BlobStore; + + constructor(opts: ReadThroughBlobStoreOptions) { + super(opts); + this.baseStore = opts.baseStore; + this.backingStore = opts.backingStore; + } + + async store( + blob: MediaBlob, + opts: BlobStoreStoreOptions = {} + ): Promise { + const originalUri = await blob.asUri(); + const newBlob = await this.backingStore.store(blob, opts); + if (newBlob) { + await this.baseStore.mset([[originalUri, newBlob]]); + } + return newBlob; + } + + mdelete(keys: string[]): Promise { + return this.baseStore.mdelete(keys); + } + + mget(keys: string[]): Promise<(MediaBlob | undefined)[]> { + return this.baseStore.mget(keys); + } + + mset(_keyValuePairs: [string, MediaBlob][]): Promise { + throw new Error("Do not call ReadThroughBlobStore.mset directly"); + } + + yieldKeys(prefix: string | undefined): AsyncGenerator { + return this.baseStore.yieldKeys(prefix); + } +} + +export class SimpleWebBlobStore extends BlobStore { + _notImplementedException() { + throw new Error("Not implemented for SimpleWebBlobStore"); + } + + async hasValidPath( + path: string | undefined, + _opts?: BlobStoreStoreOptions + ): Promise { + return ( + (await super.hasValidPath(path, { pathPrefix: "https://" })) || + (await super.hasValidPath(path, { pathPrefix: "http://" })) + ); + } + + async _fetch(url: string): Promise { + const ret = new MediaBlob({ + path: url, + }); + const metadata: Record = {}; + const fetchOptions = { + method: "GET", + }; + const res = await fetch(url, fetchOptions); + metadata.status = res.status; + + const headers: Record = {}; + for (const [key, value] of res.headers.entries()) { + headers[key] = value; + } + metadata.headers = headers; + + metadata.ok = res.ok; + if (res.ok) { + const resMediaBlob = await MediaBlob.fromBlob(await res.blob()); + ret.data = resMediaBlob.data; + } + + ret.metadata = metadata; + return ret; + } + + async mget(keys: string[]): Promise<(MediaBlob | undefined)[]> { + const blobMap = keys.map(this._fetch); + return await Promise.all(blobMap); + } + + async mdelete(_keys: string[]): Promise { + this._notImplementedException(); + } + + async mset(_keyValuePairs: [string, MediaBlob][]): Promise { + this._notImplementedException(); + } + + async *yieldKeys(_prefix: string | undefined): AsyncGenerator { + this._notImplementedException(); + yield ""; + } +} + +/** + * A blob "store" that works with data: URLs that will turn the URL into + * a blob. + */ +export class DataBlobStore extends BlobStore { + _notImplementedException() { + throw new Error("Not implemented for DataBlobStore"); + } + + hasValidPath(path: string, _opts?: BlobStoreStoreOptions): Promise { + return super.hasValidPath(path, { pathPrefix: "data:" }); + } + + _fetch(url: string): MediaBlob { + return MediaBlob.fromDataUrl(url); + } + + async mget(keys: string[]): Promise<(MediaBlob | undefined)[]> { + const blobMap = keys.map(this._fetch); + return blobMap; + } + + async mdelete(_keys: string[]): Promise { + this._notImplementedException(); + } + + async mset(_keyValuePairs: [string, MediaBlob][]): Promise { + this._notImplementedException(); + } + + async *yieldKeys(_prefix: string | undefined): AsyncGenerator { + this._notImplementedException(); + yield ""; + } +} + +export interface MediaManagerConfiguration { + /** + * A store that, given a common URI, returns the corresponding MediaBlob. + * The returned MediaBlob may have a different URI. + * In many cases, this will be a ReadThroughStore or something similar + * that has a cached version of the MediaBlob, but also a way to get + * a new (or refreshed) version. + */ + store: BlobStore; + + /** + * BlobStores that can resolve a URL into the MediaBlob to save + * in the canonical store. This list is evaluated in order. + * If not provided, a default list (which involves a DataBlobStore + * and a SimpleWebBlobStore) will be used. + */ + resolvers?: BlobStore[]; +} + +/** + * Responsible for converting a URI (typically a web URL) into a MediaBlob. + * Allows for aliasing / caching of the requested URI and what it resolves to. + * This MediaBlob is expected to be usable to provide to an LLM, either + * through the Base64 of the media or through a canonical URI that the LLM + * supports. + */ +export class MediaManager { + store: BlobStore; + + resolvers: BlobStore[] | undefined; + + constructor(config: MediaManagerConfiguration) { + this.store = config.store; + this.resolvers = config.resolvers; + } + + defaultResolvers(): BlobStore[] { + return [new DataBlobStore({}), new SimpleWebBlobStore({})]; + } + + async _isInvalid(blob: MediaBlob | undefined): Promise { + return typeof blob === "undefined"; + } + + /** + * Given the public URI, load what is at this URI and save it + * in the store. + * @param uri The URI to resolve using the resolver + * @return A canonical MediaBlob for this URI + */ + async _resolveAndSave(uri: string): Promise { + let resolvedBlob: MediaBlob | undefined; + + const resolvers = this.resolvers || this.defaultResolvers(); + for (let co = 0; co < resolvers.length; co += 1) { + const resolver = resolvers[co]; + if (await resolver.hasValidPath(uri)) { + resolvedBlob = await resolver.fetch(uri); + } + } + + if (resolvedBlob) { + return await this.store.store(resolvedBlob); + } else { + return new MediaBlob({}); + } + } + + async getMediaBlob(uri: string): Promise { + const aliasBlob = await this.store.fetch(uri); + const ret = (await this._isInvalid(aliasBlob)) + ? await this._resolveAndSave(uri) + : (aliasBlob as MediaBlob); + return ret; + } +} diff --git a/libs/langchain-google-common/src/llms.ts b/libs/langchain-google-common/src/llms.ts index 347098177186..e9c267aaaf65 100644 --- a/libs/langchain-google-common/src/llms.ts +++ b/libs/langchain-google-common/src/llms.ts @@ -21,13 +21,7 @@ import { copyAIModelParams, copyAndValidateModelParamsInto, } from "./utils/common.js"; -import { - chunkToString, - messageContentToParts, - safeResponseToBaseMessage, - safeResponseToString, - DefaultGeminiSafetyHandler, -} from "./utils/gemini.js"; +import { DefaultGeminiSafetyHandler } from "./utils/gemini.js"; import { ApiKeyGoogleAuth, GoogleAbstractedClient } from "./auth.js"; import { ensureParams } from "./utils/failed_handler.js"; import { ChatGoogleBase } from "./chat_models.js"; @@ -39,11 +33,11 @@ class GoogleLLMConnection extends AbstractGoogleLLMConnection< MessageContent, AuthOptions > { - formatContents( + async formatContents( input: MessageContent, _parameters: GoogleAIModelParams - ): GeminiContent[] { - const parts = messageContentToParts(input); + ): Promise { + const parts = await this.api.messageContentToParts(input); const contents: GeminiContent[] = [ { role: "user", // Required by Vertex AI @@ -189,7 +183,10 @@ export abstract class GoogleBaseLLM ): Promise { const parameters = copyAIModelParams(this, options); const result = await this.connection.request(prompt, parameters, options); - const ret = safeResponseToString(result, this.safetyHandler); + const ret = this.connection.api.safeResponseToString( + result, + this.safetyHandler + ); return ret; } @@ -234,7 +231,7 @@ export abstract class GoogleBaseLLM const proxyChat = this.createProxyChat(); try { for await (const chunk of proxyChat._streamIterator(input, options)) { - const stringValue = chunkToString(chunk); + const stringValue = this.connection.api.chunkToString(chunk); const generationChunk = new GenerationChunk({ text: stringValue, }); @@ -267,7 +264,10 @@ export abstract class GoogleBaseLLM {}, options as BaseLanguageModelCallOptions ); - const ret = safeResponseToBaseMessage(result, this.safetyHandler); + const ret = this.connection.api.safeResponseToBaseMessage( + result, + this.safetyHandler + ); return ret; } diff --git a/libs/langchain-google-common/src/tests/chat_models.test.ts b/libs/langchain-google-common/src/tests/chat_models.test.ts index dda4b68033ce..9da477df3e0e 100644 --- a/libs/langchain-google-common/src/tests/chat_models.test.ts +++ b/libs/langchain-google-common/src/tests/chat_models.test.ts @@ -9,6 +9,7 @@ import { SystemMessage, ToolMessage, } from "@langchain/core/messages"; +import { InMemoryStore } from "@langchain/core/stores"; import { z } from "zod"; import { zodToJsonSchema } from "zod-to-json-schema"; @@ -17,6 +18,12 @@ import { authOptions, MockClient, MockClientAuthInfo, mockId } from "./mock.js"; import { GeminiTool, GoogleAIBaseLLMInput } from "../types.js"; import { GoogleAbstractedClient } from "../auth.js"; import { GoogleAISafetyError } from "../utils/safety.js"; +import { + BackedBlobStore, + MediaBlob, + MediaManager, + ReadThroughBlobStore, +} from "../experimental/utils/media_core.js"; import { removeAdditionalProperties } from "../utils/zod_to_gemini_parameters.js"; class ChatGoogle extends ChatGoogleBase { @@ -502,10 +509,6 @@ describe("Mock ChatGoogle", () => { expect(caught).toEqual(true); }); - /* - * Images aren't supported (yet) by Gemini, but a one-round with - * image should work ok. - */ test("3. invoke - images", async () => { // eslint-disable-next-line @typescript-eslint/no-explicit-any const record: Record = {}; @@ -517,7 +520,7 @@ describe("Mock ChatGoogle", () => { }; const model = new ChatGoogle({ authOptions, - model: "gemini-pro-vision", + model: "gemini-1.5-flash", }); const message: MessageContentComplex[] = [ @@ -552,6 +555,200 @@ describe("Mock ChatGoogle", () => { expect(result.content).toBe("A blue square."); }); + test("3. invoke - media - invalid", async () => { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const record: Record = {}; + const projectId = mockId(); + const authOptions: MockClientAuthInfo = { + record, + projectId, + resultFile: "chat-3-mock.json", + }; + const model = new ChatGoogle({ + authOptions, + model: "gemini-1.5-flash", + }); + + const message: MessageContentComplex[] = [ + { + type: "text", + text: "What is in this image?", + }, + { + type: "media", + fileUri: "mock://example.com/blue-box.png", + }, + ]; + + const messages: BaseMessage[] = [ + new HumanMessageChunk({ content: message }), + ]; + + try { + const result = await model.invoke(messages); + expect(result).toBeUndefined(); + } catch (e) { + expect((e as Error).message).toEqual("Invalid media content"); + } + }); + + test("3. invoke - media - no manager", async () => { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const record: Record = {}; + const projectId = mockId(); + const authOptions: MockClientAuthInfo = { + record, + projectId, + resultFile: "chat-3-mock.json", + }; + const model = new ChatGoogle({ + authOptions, + model: "gemini-1.5-flash", + }); + + const message: MessageContentComplex[] = [ + { + type: "text", + text: "What is in this image?", + }, + { + type: "media", + fileUri: "mock://example.com/blue-box.png", + mimeType: "image/png", + }, + ]; + + const messages: BaseMessage[] = [ + new HumanMessageChunk({ content: message }), + ]; + + const result = await model.invoke(messages); + + console.log(JSON.stringify(record.opts, null, 1)); + + expect(record.opts).toHaveProperty("data"); + expect(record.opts.data).toHaveProperty("contents"); + expect(record.opts.data.contents).toHaveLength(1); + expect(record.opts.data.contents[0]).toHaveProperty("parts"); + + const parts = record?.opts?.data?.contents[0]?.parts; + expect(parts).toHaveLength(2); + expect(parts[0]).toHaveProperty("text"); + expect(parts[1]).toHaveProperty("fileData"); + expect(parts[1].fileData).toHaveProperty("mimeType"); + expect(parts[1].fileData).toHaveProperty("fileUri"); + + expect(result.content).toBe("A blue square."); + }); + + test("3. invoke - media - manager", async () => { + class MemStore extends InMemoryStore { + get length() { + return Object.keys(this.store).length; + } + } + + const aliasMemory = new MemStore(); + const aliasStore = new BackedBlobStore({ + backingStore: aliasMemory, + defaultFetchOptions: { + actionIfBlobMissing: undefined, + }, + }); + const canonicalMemory = new MemStore(); + const canonicalStore = new BackedBlobStore({ + backingStore: canonicalMemory, + defaultStoreOptions: { + pathPrefix: "canonical://store/", + actionIfInvalid: "prefixPath", + }, + defaultFetchOptions: { + actionIfBlobMissing: undefined, + }, + }); + const blobStore = new ReadThroughBlobStore({ + baseStore: aliasStore, + backingStore: canonicalStore, + }); + const resolverMemory = new MemStore(); + const resolver = new BackedBlobStore({ + backingStore: resolverMemory, + defaultFetchOptions: { + actionIfBlobMissing: "emptyBlob", + }, + }); + const mediaManager = new MediaManager({ + store: blobStore, + resolvers: [resolver], + }); + + async function store(path: string, text: string): Promise { + const type = path.endsWith(".png") ? "image/png" : "text/plain"; + const blob = new MediaBlob({ + data: { + value: text, + type, + }, + path, + }); + await resolver.store(blob); + } + await store("resolve://host/foo", "fooing"); + await store("resolve://host2/bar/baz", "barbazing"); + await store("resolve://host/foo/blue-box.png", "png"); + + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const record: Record = {}; + const projectId = mockId(); + const authOptions: MockClientAuthInfo = { + record, + projectId, + resultFile: "chat-3-mock.json", + }; + const model = new ChatGoogle({ + authOptions, + model: "gemini-1.5-flash", + mediaManager, + }); + + const message: MessageContentComplex[] = [ + { + type: "text", + text: "What is in this image?", + }, + { + type: "media", + fileUri: "resolve://host/foo/blue-box.png", + }, + ]; + + const messages: BaseMessage[] = [ + new HumanMessageChunk({ content: message }), + ]; + + const result = await model.invoke(messages); + + console.log(JSON.stringify(record.opts, null, 1)); + + expect(record.opts).toHaveProperty("data"); + expect(record.opts.data).toHaveProperty("contents"); + expect(record.opts.data.contents).toHaveLength(1); + expect(record.opts.data.contents[0]).toHaveProperty("parts"); + + const parts = record?.opts?.data?.contents[0]?.parts; + expect(parts).toHaveLength(2); + expect(parts[0]).toHaveProperty("text"); + expect(parts[1]).toHaveProperty("fileData"); + expect(parts[1].fileData).toHaveProperty("mimeType"); + expect(parts[1].fileData.mimeType).toEqual("image/png"); + expect(parts[1].fileData).toHaveProperty("fileUri"); + expect(parts[1].fileData.fileUri).toEqual( + "canonical://store/host/foo/blue-box.png" + ); + + expect(result.content).toBe("A blue square."); + }); + test("4. Functions Bind - Gemini format request", async () => { // eslint-disable-next-line @typescript-eslint/no-explicit-any const record: Record = {}; diff --git a/libs/langchain-google-common/src/tests/utils.test.ts b/libs/langchain-google-common/src/tests/utils.test.ts index c0398a218c67..547392c397e7 100644 --- a/libs/langchain-google-common/src/tests/utils.test.ts +++ b/libs/langchain-google-common/src/tests/utils.test.ts @@ -1,85 +1,419 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ -import { expect, test } from "@jest/globals"; +import { beforeEach, expect, test } from "@jest/globals"; +import { InMemoryStore } from "@langchain/core/stores"; +import { SerializedConstructor } from "@langchain/core/load/serializable"; +import { load } from "@langchain/core/load"; import { z } from "zod"; import { zodToGeminiParameters } from "../utils/zod_to_gemini_parameters.js"; +import { + BackedBlobStore, + BlobStore, + MediaBlob, + MediaManager, + ReadThroughBlobStore, + SimpleWebBlobStore, +} from "../experimental/utils/media_core.js"; import { ReadableJsonStream } from "../utils/stream.js"; -test("zodToGeminiParameters can convert zod schema to gemini schema", () => { - const zodSchema = z - .object({ - operation: z - .enum(["add", "subtract", "multiply", "divide"]) - .describe("The type of operation to execute"), - number1: z.number().describe("The first number to operate on."), - number2: z.number().describe("The second number to operate on."), - childObject: z.object({}), - }) - .describe("A simple calculator tool"); - - const convertedSchema = zodToGeminiParameters(zodSchema); - - expect(convertedSchema.type).toBe("object"); - expect(convertedSchema.description).toBe("A simple calculator tool"); - expect((convertedSchema as any).additionalProperties).toBeUndefined(); - expect(convertedSchema.properties).toEqual({ - operation: { - type: "string", - enum: ["add", "subtract", "multiply", "divide"], - description: "The type of operation to execute", - }, - number1: { - type: "number", - description: "The first number to operate on.", - }, - number2: { - type: "number", - description: "The second number to operate on.", - }, - childObject: { - type: "object", - properties: {}, - }, +describe("zodToGeminiParameters", () => { + test("can convert zod schema to gemini schema", () => { + const zodSchema = z + .object({ + operation: z + .enum(["add", "subtract", "multiply", "divide"]) + .describe("The type of operation to execute"), + number1: z.number().describe("The first number to operate on."), + number2: z.number().describe("The second number to operate on."), + childObject: z.object({}), + }) + .describe("A simple calculator tool"); + + const convertedSchema = zodToGeminiParameters(zodSchema); + + expect(convertedSchema.type).toBe("object"); + expect(convertedSchema.description).toBe("A simple calculator tool"); + expect((convertedSchema as any).additionalProperties).toBeUndefined(); + expect(convertedSchema.properties).toEqual({ + operation: { + type: "string", + enum: ["add", "subtract", "multiply", "divide"], + description: "The type of operation to execute", + }, + number1: { + type: "number", + description: "The first number to operate on.", + }, + number2: { + type: "number", + description: "The second number to operate on.", + }, + childObject: { + type: "object", + properties: {}, + }, + }); + expect(convertedSchema.required).toEqual([ + "operation", + "number1", + "number2", + "childObject", + ]); + }); + + test("removes additional properties from arrays", () => { + const zodSchema = z + .object({ + people: z + .object({ + name: z.string().describe("The name of a person"), + }) + .array() + .describe("person elements"), + }) + .describe("A list of people"); + + const convertedSchema = zodToGeminiParameters(zodSchema); + expect(convertedSchema.type).toBe("object"); + expect(convertedSchema.description).toBe("A list of people"); + expect((convertedSchema as any).additionalProperties).toBeUndefined(); + + const peopleSchema = convertedSchema?.properties?.people; + expect(peopleSchema).not.toBeUndefined(); + + if (peopleSchema !== undefined) { + expect(peopleSchema.type).toBe("array"); + expect((peopleSchema as any).additionalProperties).toBeUndefined(); + expect(peopleSchema.description).toBe("person elements"); + } + + const arrayItemsSchema = peopleSchema?.items; + expect(arrayItemsSchema).not.toBeUndefined(); + if (arrayItemsSchema !== undefined) { + expect(arrayItemsSchema.type).toBe("object"); + expect((arrayItemsSchema as any).additionalProperties).toBeUndefined(); + } }); - expect(convertedSchema.required).toEqual([ - "operation", - "number1", - "number2", - "childObject", - ]); }); -test("zodToGeminiParameters removes additional properties from arrays", () => { - const zodSchema = z - .object({ - people: z - .object({ - name: z.string().describe("The name of a person"), - }) - .array() - .describe("person elements"), - }) - .describe("A list of people"); - - const convertedSchema = zodToGeminiParameters(zodSchema); - expect(convertedSchema.type).toBe("object"); - expect(convertedSchema.description).toBe("A list of people"); - expect((convertedSchema as any).additionalProperties).toBeUndefined(); - - const peopleSchema = convertedSchema?.properties?.people; - expect(peopleSchema).not.toBeUndefined(); - - if (peopleSchema !== undefined) { - expect(peopleSchema.type).toBe("array"); - expect((peopleSchema as any).additionalProperties).toBeUndefined(); - expect(peopleSchema.description).toBe("person elements"); - } - - const arrayItemsSchema = peopleSchema?.items; - expect(arrayItemsSchema).not.toBeUndefined(); - if (arrayItemsSchema !== undefined) { - expect(arrayItemsSchema.type).toBe("object"); - expect((arrayItemsSchema as any).additionalProperties).toBeUndefined(); - } +describe("media core", () => { + test("MediaBlob plain", async () => { + const blob = new Blob(["This is a test"], { type: "text/plain" }); + const mblob = await MediaBlob.fromBlob(blob); + expect(mblob.dataType).toEqual("text/plain"); + expect(mblob.mimetype).toEqual("text/plain"); + expect(mblob.encoding).toEqual("utf-8"); + expect(await mblob.asString()).toEqual("This is a test"); + }); + + test("MediaBlob charset", async () => { + const blob = new Blob(["This is a test"], { + type: "text/plain; charset=US-ASCII", + }); + const mblob = await MediaBlob.fromBlob(blob); + expect(mblob.dataType).toEqual("text/plain; charset=us-ascii"); + expect(mblob.mimetype).toEqual("text/plain"); + expect(mblob.encoding).toEqual("us-ascii"); + expect(await mblob.asString()).toEqual("This is a test"); + }); + + test("MediaBlob fromDataUrl", async () => { + const blobData = "This is a test"; + const blobMimeType = "text/plain"; + const blobDataType = `${blobMimeType}; charset=US-ASCII`; + const blob = new Blob([blobData], { + type: blobDataType, + }); + const mblob = await MediaBlob.fromBlob(blob); + const dataUrl = await mblob.asDataUrl(); + const dblob = MediaBlob.fromDataUrl(dataUrl); + expect(await dblob.asString()).toEqual(blobData); + expect(dblob.mimetype).toEqual(blobMimeType); + }); + + test("MediaBlob serialize", async () => { + const blob = new Blob(["This is a test"], { type: "text/plain" }); + const mblob = await MediaBlob.fromBlob(blob); + console.log("serialize mblob", mblob); + const serialized = mblob.toJSON() as SerializedConstructor; + console.log("serialized", serialized); + expect(serialized.kwargs).toHaveProperty("data"); + expect(serialized.kwargs.data.value).toEqual("VGhpcyBpcyBhIHRlc3Q="); + }); + + test("MediaBlob deserialize", async () => { + const serialized: SerializedConstructor = { + lc: 1, + type: "constructor", + id: [ + "langchain", + "google_common", + "experimental", + "utils", + "media_core", + "MediaBlob", + ], + kwargs: { + data: { + value: "VGhpcyBpcyBhIHRlc3Q=", + type: "text/plain", + }, + }, + }; + const mblob: MediaBlob = await load(JSON.stringify(serialized), { + importMap: { + google_common__experimental__utils__media_core: await import( + "../experimental/utils/media_core.js" + ), + }, + }); + console.log("deserialize mblob", mblob); + expect(mblob.dataType).toEqual("text/plain"); + expect(await mblob.asString()).toEqual("This is a test"); + }); + + test("SimpleWebBlobStore fetch", async () => { + const webStore = new SimpleWebBlobStore(); + const exampleBlob = await webStore.fetch("http://example.com/"); + console.log(exampleBlob); + expect(exampleBlob?.mimetype).toEqual("text/html"); + expect(exampleBlob?.encoding).toEqual("utf-8"); + expect(exampleBlob?.size).toBeGreaterThan(0); + expect(exampleBlob?.metadata).toBeDefined(); + expect(exampleBlob?.metadata?.ok).toBeTruthy(); + expect(exampleBlob?.metadata?.status).toEqual(200); + }); + + describe("BackedBlobStore", () => { + test("simple", async () => { + const backingStore = new InMemoryStore(); + const store = new BackedBlobStore({ + backingStore, + }); + const data = new Blob(["This is a test"], { type: "text/plain" }); + const path = "simple://foo"; + const blob = await MediaBlob.fromBlob(data, { path }); + const storedBlob = await store.store(blob); + expect(storedBlob).toBeDefined(); + const fetchedBlob = await store.fetch(path); + expect(fetchedBlob).toBeDefined(); + }); + + test("missing undefined", async () => { + const backingStore = new InMemoryStore(); + const store = new BackedBlobStore({ + backingStore, + }); + const path = "simple://foo"; + const fetchedBlob = await store.fetch(path); + expect(fetchedBlob).toBeUndefined(); + }); + + test("missing emptyBlob defaultConfig", async () => { + const backingStore = new InMemoryStore(); + const store = new BackedBlobStore({ + backingStore, + defaultFetchOptions: { + actionIfBlobMissing: "emptyBlob", + }, + }); + const path = "simple://foo"; + const fetchedBlob = await store.fetch(path); + expect(fetchedBlob).toBeDefined(); + expect(fetchedBlob?.size).toEqual(0); + expect(fetchedBlob?.path).toEqual(path); + }); + + test("missing undefined fetch", async () => { + const backingStore = new InMemoryStore(); + const store = new BackedBlobStore({ + backingStore, + defaultFetchOptions: { + actionIfBlobMissing: "emptyBlob", + }, + }); + const path = "simple://foo"; + const fetchedBlob = await store.fetch(path, { + actionIfBlobMissing: undefined, + }); + expect(fetchedBlob).toBeUndefined(); + }); + + test("invalid undefined", async () => { + const backingStore = new InMemoryStore(); + const store = new BackedBlobStore({ + backingStore, + defaultStoreOptions: { + pathPrefix: "example://bar/", + }, + }); + const path = "simple://foo"; + const data = new Blob(["This is a test"], { type: "text/plain" }); + const blob = await MediaBlob.fromBlob(data, { path }); + const storedBlob = await store.store(blob); + expect(storedBlob).toBeUndefined(); + }); + + test("invalid ignore", async () => { + const backingStore = new InMemoryStore(); + const store = new BackedBlobStore({ + backingStore, + defaultStoreOptions: { + actionIfInvalid: "ignore", + pathPrefix: "example://bar/", + }, + }); + const path = "simple://foo"; + const data = new Blob(["This is a test"], { type: "text/plain" }); + const blob = await MediaBlob.fromBlob(data, { path }); + const storedBlob = await store.store(blob); + expect(storedBlob).toBeDefined(); + expect(storedBlob?.path).toEqual(path); + expect(storedBlob?.metadata).toBeUndefined(); + }); + + test("invalid prefixPath", async () => { + const backingStore = new InMemoryStore(); + const store = new BackedBlobStore({ + backingStore, + defaultStoreOptions: { + actionIfInvalid: "prefixPath", + pathPrefix: "example://bar/", + }, + }); + const path = "simple://foo"; + const data = new Blob(["This is a test"], { type: "text/plain" }); + const blob = await MediaBlob.fromBlob(data, { path }); + const storedBlob = await store.store(blob); + expect(storedBlob?.path).toEqual("example://bar/foo"); + expect(await storedBlob?.asString()).toEqual("This is a test"); + expect(storedBlob?.metadata?.langchainOldPath).toEqual(path); + }); + + test("invalid prefixUuid", async () => { + const backingStore = new InMemoryStore(); + const store = new BackedBlobStore({ + backingStore, + defaultStoreOptions: { + actionIfInvalid: "prefixUuid4", + pathPrefix: "example://bar/", + }, + }); + const path = "simple://foo"; + const data = new Blob(["This is a test"], { type: "text/plain" }); + const metadata = { + alpha: "one", + bravo: "two", + }; + const blob = await MediaBlob.fromBlob(data, { path, metadata }); + const storedBlob = await store.store(blob); + expect(storedBlob?.path).toMatch( + /example:\/\/bar\/[a-f0-9]{8}(-[a-f0-9]{4}){3}-[a-f0-9]{12}$/i + ); + expect(storedBlob?.size).toEqual(14); + expect(await storedBlob?.asString()).toEqual("This is a test"); + expect(storedBlob?.metadata?.alpha).toEqual("one"); + expect(storedBlob?.metadata?.langchainOldPath).toEqual(path); + }); + }); + + describe("MediaManager", () => { + class MemStore extends InMemoryStore { + get length() { + return Object.keys(this.store).length; + } + } + + let mediaManager: MediaManager; + let aliasMemory: MemStore; + let canonicalMemory: MemStore; + let resolverMemory: MemStore; + + async function store( + blobStore: BlobStore, + path: string, + text: string + ): Promise { + const data = new Blob([text], { type: "text/plain" }); + const blob = await MediaBlob.fromBlob(data, { path }); + await blobStore.store(blob); + } + + beforeEach(async () => { + aliasMemory = new MemStore(); + const aliasStore = new BackedBlobStore({ + backingStore: aliasMemory, + defaultFetchOptions: { + actionIfBlobMissing: undefined, + }, + }); + canonicalMemory = new MemStore(); + const canonicalStore = new BackedBlobStore({ + backingStore: canonicalMemory, + defaultStoreOptions: { + pathPrefix: "canonical://store/", + actionIfInvalid: "prefixPath", + }, + defaultFetchOptions: { + actionIfBlobMissing: undefined, + }, + }); + resolverMemory = new MemStore(); + const resolver = new BackedBlobStore({ + backingStore: resolverMemory, + defaultFetchOptions: { + actionIfBlobMissing: "emptyBlob", + }, + }); + const mediaStore = new ReadThroughBlobStore({ + baseStore: aliasStore, + backingStore: canonicalStore, + }); + mediaManager = new MediaManager({ + store: mediaStore, + resolvers: [resolver], + }); + await store(resolver, "resolve://host/foo", "fooing"); + await store(resolver, "resolve://host2/bar/baz", "barbazing"); + }); + + test("environment", async () => { + expect(resolverMemory.length).toEqual(2); + const fooBlob = await mediaManager.resolvers?.[0]?.fetch( + "resolve://host/foo" + ); + expect(await fooBlob?.asString()).toEqual("fooing"); + }); + + test("simple", async () => { + const uri = "resolve://host/foo"; + const curi = "canonical://store/host/foo"; + const blob = await mediaManager.getMediaBlob(uri); + expect(await blob?.asString()).toEqual("fooing"); + expect(blob?.path).toEqual(curi); + + // In the alias store, + // we should be able to fetch it by the resolve uri, but the + // path in the blob itself should be the canonical uri + expect(aliasMemory.length).toEqual(1); + const mediaStore: ReadThroughBlobStore = + mediaManager.store as ReadThroughBlobStore; + const aliasBlob = await mediaStore.baseStore.fetch(uri); + expect(aliasBlob).toBeDefined(); + expect(aliasBlob?.path).toEqual(curi); + expect(await aliasBlob?.asString()).toEqual("fooing"); + + // For the canonical store, + // fetching it by the resolve uri should fail + // but fetching it by the canonical uri should succeed + expect(canonicalMemory.length).toEqual(1); + const canonicalBlobU = await mediaStore.backingStore.fetch(uri); + expect(canonicalBlobU).toBeUndefined(); + const canonicalBlob = await mediaStore.backingStore.fetch(curi); + expect(canonicalBlob).toBeDefined(); + expect(canonicalBlob?.path).toEqual(curi); + expect(await canonicalBlob?.asString()).toEqual("fooing"); + }); + }); }); function toUint8Array(data: string): Uint8Array { diff --git a/libs/langchain-google-common/src/types.ts b/libs/langchain-google-common/src/types.ts index 7721b5136704..4fecd254693b 100644 --- a/libs/langchain-google-common/src/types.ts +++ b/libs/langchain-google-common/src/types.ts @@ -4,6 +4,7 @@ import type { BindToolsInput, } from "@langchain/core/language_models/chat_models"; import type { JsonStream } from "./utils/stream.js"; +import { MediaManager } from "./experimental/utils/media_core.js"; /** * Parameters needed to setup the client connection. @@ -147,7 +148,8 @@ export interface GoogleAIBaseLLMInput extends BaseLLMParams, GoogleConnectionParams, GoogleAIModelParams, - GoogleAISafetyParams {} + GoogleAISafetyParams, + GeminiAPIConfig {} export interface GoogleAIBaseLanguageModelCallOptions extends BaseChatModelCallOptions, @@ -172,6 +174,10 @@ export interface GoogleResponse { data: any; } +export interface GoogleRawResponse extends GoogleResponse { + data: Blob; +} + export interface GeminiPartText { text: string; } @@ -183,7 +189,6 @@ export interface GeminiPartInlineData { }; } -// Vertex AI only export interface GeminiPartFileData { fileData: { mimeType: string; @@ -342,3 +347,7 @@ export interface GeminiJsonSchemaDirty extends GeminiJsonSchema { properties?: Record; additionalProperties?: boolean; } + +export interface GeminiAPIConfig { + mediaManager?: MediaManager; +} diff --git a/libs/langchain-google-common/src/utils/gemini.ts b/libs/langchain-google-common/src/utils/gemini.ts index d230203a85de..aab0699cec56 100644 --- a/libs/langchain-google-common/src/utils/gemini.ts +++ b/libs/langchain-google-common/src/utils/gemini.ts @@ -19,7 +19,6 @@ import { ChatGeneration, ChatGenerationChunk, ChatResult, - Generation, } from "@langchain/core/outputs"; import { ToolCallChunk } from "@langchain/core/messages/tool"; import type { @@ -34,8 +33,32 @@ import type { GenerateContentResponseData, GoogleAISafetyHandler, GeminiPartFunctionCall, + GeminiAPIConfig, } from "../types.js"; import { GoogleAISafetyError } from "./safety.js"; +import { MediaBlob } from "../experimental/utils/media_core.js"; + +export interface FunctionCall { + name: string; + arguments: string; +} + +export interface ToolCall { + id: string; + type: "function"; + function: FunctionCall; +} + +export interface FunctionCallRaw { + name: string; + arguments: object; +} + +export interface ToolCallRaw { + id: string; + type: "function"; + function: FunctionCallRaw; +} const extractMimeType = ( str: string @@ -49,671 +72,686 @@ const extractMimeType = ( return null; }; -function messageContentText( - content: MessageContentText -): GeminiPartText | null { - if (content?.text && content?.text.length > 0) { - return { - text: content.text, - }; - } else { - return null; +export function getGeminiAPI(config?: GeminiAPIConfig) { + function messageContentText( + content: MessageContentText + ): GeminiPartText | null { + if (content?.text && content?.text.length > 0) { + return { + text: content.text, + }; + } else { + return null; + } } -} -function messageContentImageUrl( - content: MessageContentImageUrl -): GeminiPartInlineData | GeminiPartFileData { - const url: string = - typeof content.image_url === "string" - ? content.image_url - : content.image_url.url; - if (!url) { - throw new Error("Missing Image URL"); - } + function messageContentImageUrl( + content: MessageContentImageUrl + ): GeminiPartInlineData | GeminiPartFileData { + const url: string = + typeof content.image_url === "string" + ? content.image_url + : content.image_url.url; + if (!url) { + throw new Error("Missing Image URL"); + } - const mineTypeAndData = extractMimeType(url); - if (mineTypeAndData) { - return { - inlineData: mineTypeAndData, - }; - } else { - // FIXME - need some way to get mime type - return { - fileData: { - mimeType: "image/png", - fileUri: url, - }, - }; + const mineTypeAndData = extractMimeType(url); + if (mineTypeAndData) { + return { + inlineData: mineTypeAndData, + }; + } else { + // FIXME - need some way to get mime type + return { + fileData: { + mimeType: "image/png", + fileUri: url, + }, + }; + } } -} -function messageContentMedia( - // eslint-disable-next-line @typescript-eslint/no-explicit-any - content: Record -): GeminiPartInlineData | GeminiPartFileData { - if ("mimeType" in content && "data" in content) { - return { - inlineData: { - mimeType: content.mimeType, - data: content.data, - }, - }; - } else if ("mimeType" in content && "fileUri" in content) { + async function blobToFileData(blob: MediaBlob): Promise { return { fileData: { - mimeType: content.mimeType, - fileUri: content.fileUri, + fileUri: blob.path!, + mimeType: blob.mimetype, }, }; } - throw new Error("Invalid media content"); -} + async function fileUriContentToBlob( + uri: string + ): Promise { + return config?.mediaManager?.getMediaBlob(uri); + } -export function messageContentToParts(content: MessageContent): GeminiPart[] { - // Convert a string to a text type MessageContent if needed - const messageContent: MessageContent = - typeof content === "string" - ? [ - { - type: "text", - text: content, - }, - ] - : content; - - // eslint-disable-next-line array-callback-return - const parts: GeminiPart[] = messageContent - .map((content) => { - switch (content.type) { - case "text": - if ("text" in content) { - return messageContentText(content as MessageContentText); - } - break; - case "image_url": - if ("image_url" in content) { - // Type guard for MessageContentImageUrl - return messageContentImageUrl(content as MessageContentImageUrl); - } - break; - case "media": - return messageContentMedia(content); - default: - throw new Error( - `Unsupported type received while converting message to message parts` - ); - } - throw new Error( - `Cannot coerce "${content.type}" message part into a string.` - ); - }) - .reduce((acc: GeminiPart[], val: GeminiPart | null | undefined) => { - if (val) { - return [...acc, val]; - } else { - return acc; + async function messageContentMedia( + // eslint-disable-next-line @typescript-eslint/no-explicit-any + content: Record + ): Promise { + if ("mimeType" in content && "data" in content) { + return { + inlineData: { + mimeType: content.mimeType, + data: content.data, + }, + }; + } else if ("mimeType" in content && "fileUri" in content) { + return { + fileData: { + mimeType: content.mimeType, + fileUri: content.fileUri, + }, + }; + } else { + const uri = content.fileUri; + const blob = await fileUriContentToBlob(uri); + if (blob) { + return await blobToFileData(blob); } - }, []); - - return parts; -} + } -function messageToolCallsToParts(toolCalls: ToolCall[]): GeminiPart[] { - if (!toolCalls || toolCalls.length === 0) { - return []; + throw new Error("Invalid media content"); } - return toolCalls.map((tool: ToolCall) => { - let args = {}; - if (tool?.function?.arguments) { - const argStr = tool.function.arguments; - args = JSON.parse(argStr); + async function messageContentComplexToPart( + content: MessageContentComplex + ): Promise { + switch (content.type) { + case "text": + if ("text" in content) { + return messageContentText(content as MessageContentText); + } + break; + case "image_url": + if ("image_url" in content) { + // Type guard for MessageContentImageUrl + return messageContentImageUrl(content as MessageContentImageUrl); + } + break; + case "media": + return await messageContentMedia(content); + default: + throw new Error( + `Unsupported type received while converting message to message parts` + ); } - return { - functionCall: { - name: tool.function.name, - args, - }, - }; - }); -} + throw new Error( + `Cannot coerce "${content.type}" message part into a string.` + ); + } + + async function messageContentComplexToParts( + content: MessageContentComplex[] + ): Promise<(GeminiPart | null)[]> { + const contents = content.map(messageContentComplexToPart); + return Promise.all(contents); + } -function messageKwargsToParts(kwargs: Record): GeminiPart[] { - const ret: GeminiPart[] = []; + async function messageContentToParts( + content: MessageContent + ): Promise { + // Convert a string to a text type MessageContent if needed + const messageContent: MessageContentComplex[] = + typeof content === "string" + ? [ + { + type: "text", + text: content, + }, + ] + : content; + + // Get all of the parts, even those that don't correctly resolve + const allParts = await messageContentComplexToParts(messageContent); + + // Remove any invalid parts + const parts: GeminiPart[] = allParts.reduce( + (acc: GeminiPart[], val: GeminiPart | null | undefined) => { + if (val) { + return [...acc, val]; + } else { + return acc; + } + }, + [] + ); - if (kwargs?.tool_calls) { - ret.push(...messageToolCallsToParts(kwargs.tool_calls as ToolCall[])); + return parts; } - return ret; -} + function messageToolCallsToParts(toolCalls: ToolCall[]): GeminiPart[] { + if (!toolCalls || toolCalls.length === 0) { + return []; + } -function roleMessageToContent( - role: GeminiRole, - message: BaseMessage -): GeminiContent[] { - const contentParts: GeminiPart[] = messageContentToParts(message.content); - let toolParts: GeminiPart[]; - if (isAIMessage(message) && !!message.tool_calls?.length) { - toolParts = message.tool_calls.map( - (toolCall): GeminiPart => ({ + return toolCalls.map((tool: ToolCall) => { + let args = {}; + if (tool?.function?.arguments) { + const argStr = tool.function.arguments; + args = JSON.parse(argStr); + } + return { functionCall: { - name: toolCall.name, - args: toolCall.args, + name: tool.function.name, + args, }, - }) - ); - } else { - toolParts = messageKwargsToParts(message.additional_kwargs); - } - const parts: GeminiPart[] = [...contentParts, ...toolParts]; - return [ - { - role, - parts, - }, - ]; -} + }; + }); + } -function systemMessageToContent( - message: SystemMessage, - useSystemInstruction: boolean -): GeminiContent[] { - return useSystemInstruction - ? roleMessageToContent("system", message) - : [ - ...roleMessageToContent("user", message), - ...roleMessageToContent("model", new AIMessage("Ok")), - ]; -} + function messageKwargsToParts(kwargs: Record): GeminiPart[] { + const ret: GeminiPart[] = []; + + if (kwargs?.tool_calls) { + ret.push(...messageToolCallsToParts(kwargs.tool_calls as ToolCall[])); + } -function toolMessageToContent( - message: ToolMessage, - prevMessage: BaseMessage -): GeminiContent[] { - const contentStr = - typeof message.content === "string" - ? message.content - : message.content.reduce( - (acc: string, content: MessageContentComplex) => { - if (content.type === "text") { - return acc + content.text; - } else { - return acc; - } + return ret; + } + + async function roleMessageToContent( + role: GeminiRole, + message: BaseMessage + ): Promise { + const contentParts: GeminiPart[] = await messageContentToParts( + message.content + ); + let toolParts: GeminiPart[]; + if (isAIMessage(message) && !!message.tool_calls?.length) { + toolParts = message.tool_calls.map( + (toolCall): GeminiPart => ({ + functionCall: { + name: toolCall.name, + args: toolCall.args, }, - "" - ); - // Hacky :( - const responseName = - (isAIMessage(prevMessage) && !!prevMessage.tool_calls?.length - ? prevMessage.tool_calls[0].name - : prevMessage.name) ?? message.tool_call_id; - try { - const content = JSON.parse(contentStr); + }) + ); + } else { + toolParts = messageKwargsToParts(message.additional_kwargs); + } + const parts: GeminiPart[] = [...contentParts, ...toolParts]; return [ { - role: "function", - parts: [ - { - functionResponse: { - name: responseName, - response: { content }, - }, - }, - ], + role, + parts, }, ]; - } catch (_) { - return [ - { - role: "function", - parts: [ - { - functionResponse: { - name: responseName, - response: { content: contentStr }, + } + + async function systemMessageToContent( + message: SystemMessage, + useSystemInstruction: boolean + ): Promise { + return useSystemInstruction + ? roleMessageToContent("system", message) + : [ + ...(await roleMessageToContent("user", message)), + ...(await roleMessageToContent("model", new AIMessage("Ok"))), + ]; + } + + function toolMessageToContent( + message: ToolMessage, + prevMessage: BaseMessage + ): GeminiContent[] { + const contentStr = + typeof message.content === "string" + ? message.content + : message.content.reduce( + (acc: string, content: MessageContentComplex) => { + if (content.type === "text") { + return acc + content.text; + } else { + return acc; + } }, - }, - ], - }, - ]; + "" + ); + // Hacky :( + const responseName = + (isAIMessage(prevMessage) && !!prevMessage.tool_calls?.length + ? prevMessage.tool_calls[0].name + : prevMessage.name) ?? message.tool_call_id; + try { + const content = JSON.parse(contentStr); + return [ + { + role: "function", + parts: [ + { + functionResponse: { + name: responseName, + response: { content }, + }, + }, + ], + }, + ]; + } catch (_) { + return [ + { + role: "function", + parts: [ + { + functionResponse: { + name: responseName, + response: { content: contentStr }, + }, + }, + ], + }, + ]; + } } -} -export function baseMessageToContent( - message: BaseMessage, - prevMessage: BaseMessage | undefined, - useSystemInstruction: boolean -): GeminiContent[] { - const type = message._getType(); - switch (type) { - case "system": - return systemMessageToContent( - message as SystemMessage, - useSystemInstruction - ); - case "human": - return roleMessageToContent("user", message); - case "ai": - return roleMessageToContent("model", message); - case "tool": - if (!prevMessage) { - throw new Error( - "Tool messages cannot be the first message passed to the model." + async function baseMessageToContent( + message: BaseMessage, + prevMessage: BaseMessage | undefined, + useSystemInstruction: boolean + ): Promise { + const type = message._getType(); + switch (type) { + case "system": + return systemMessageToContent( + message as SystemMessage, + useSystemInstruction ); - } - return toolMessageToContent(message as ToolMessage, prevMessage); - default: - console.log(`Unsupported message type: ${type}`); - return []; + case "human": + return roleMessageToContent("user", message); + case "ai": + return roleMessageToContent("model", message); + case "tool": + if (!prevMessage) { + throw new Error( + "Tool messages cannot be the first message passed to the model." + ); + } + return toolMessageToContent(message as ToolMessage, prevMessage); + default: + console.log(`Unsupported message type: ${type}`); + return []; + } } -} -function textPartToMessageContent(part: GeminiPartText): MessageContentText { - return { - type: "text", - text: part.text, - }; -} + function textPartToMessageContent(part: GeminiPartText): MessageContentText { + return { + type: "text", + text: part.text, + }; + } -function inlineDataPartToMessageContent( - part: GeminiPartInlineData -): MessageContentImageUrl { - return { - type: "image_url", - image_url: `data:${part.inlineData.mimeType};base64,${part.inlineData.data}`, - }; -} + function inlineDataPartToMessageContent( + part: GeminiPartInlineData + ): MessageContentImageUrl { + return { + type: "image_url", + image_url: `data:${part.inlineData.mimeType};base64,${part.inlineData.data}`, + }; + } -function fileDataPartToMessageContent( - part: GeminiPartFileData -): MessageContentImageUrl { - return { - type: "image_url", - image_url: part.fileData.fileUri, - }; -} + function fileDataPartToMessageContent( + part: GeminiPartFileData + ): MessageContentImageUrl { + return { + type: "image_url", + image_url: part.fileData.fileUri, + }; + } -export function partsToMessageContent(parts: GeminiPart[]): MessageContent { - return parts - .map((part) => { - if (part === undefined || part === null) { - return null; - } else if ("text" in part) { - return textPartToMessageContent(part); - } else if ("inlineData" in part) { - return inlineDataPartToMessageContent(part); - } else if ("fileData" in part) { - return fileDataPartToMessageContent(part); - } else { - return null; - } - }) - .reduce((acc, content) => { - if (content) { - acc.push(content); - } - return acc; - }, [] as MessageContentComplex[]); -} + function partsToMessageContent(parts: GeminiPart[]): MessageContent { + return parts + .map((part) => { + if (part === undefined || part === null) { + return null; + } else if ("text" in part) { + return textPartToMessageContent(part); + } else if ("inlineData" in part) { + return inlineDataPartToMessageContent(part); + } else if ("fileData" in part) { + return fileDataPartToMessageContent(part); + } else { + return null; + } + }) + .reduce((acc, content) => { + if (content) { + acc.push(content); + } + return acc; + }, [] as MessageContentComplex[]); + } -interface FunctionCall { - name: string; - arguments: string; -} + function toolRawToTool(raw: ToolCallRaw): ToolCall { + return { + id: raw.id, + type: raw.type, + function: { + name: raw.function.name, + arguments: JSON.stringify(raw.function.arguments), + }, + }; + } -interface ToolCall { - id: string; - type: "function"; - function: FunctionCall; -} + function functionCallPartToToolRaw( + part: GeminiPartFunctionCall + ): ToolCallRaw { + return { + id: uuidv4().replace(/-/g, ""), + type: "function", + function: { + name: part.functionCall.name, + arguments: part.functionCall.args ?? {}, + }, + }; + } -interface FunctionCallRaw { - name: string; - arguments: object; -} + function partsToToolsRaw(parts: GeminiPart[]): ToolCallRaw[] { + return parts + .map((part: GeminiPart) => { + if (part === undefined || part === null) { + return null; + } else if ("functionCall" in part) { + return functionCallPartToToolRaw(part); + } else { + return null; + } + }) + .reduce((acc, content) => { + if (content) { + acc.push(content); + } + return acc; + }, [] as ToolCallRaw[]); + } -interface ToolCallRaw { - id: string; - type: "function"; - function: FunctionCallRaw; -} + function toolsRawToTools(raws: ToolCallRaw[]): ToolCall[] { + return raws.map((raw) => toolRawToTool(raw)); + } -function toolRawToTool(raw: ToolCallRaw): ToolCall { - return { - id: raw.id, - type: raw.type, - function: { - name: raw.function.name, - arguments: JSON.stringify(raw.function.arguments), - }, - }; -} + function responseToGenerateContentResponseData( + response: GoogleLLMResponse + ): GenerateContentResponseData { + if ("nextChunk" in response.data) { + throw new Error("Cannot convert Stream to GenerateContentResponseData"); + } else if (Array.isArray(response.data)) { + // Collapse the array of response data as if it was a single one + return response.data.reduce( + ( + acc: GenerateContentResponseData, + val: GenerateContentResponseData + ): GenerateContentResponseData => { + // Add all the parts + // FIXME: Handle other candidates? + const valParts = val?.candidates?.[0]?.content?.parts ?? []; + acc.candidates[0].content.parts.push(...valParts); + + // FIXME: Merge promptFeedback and safety settings + acc.promptFeedback = val.promptFeedback; + return acc; + } + ); + } else { + return response.data as GenerateContentResponseData; + } + } -function functionCallPartToToolRaw(part: GeminiPartFunctionCall): ToolCallRaw { - return { - id: uuidv4().replace(/-/g, ""), - type: "function", - function: { - name: part.functionCall.name, - arguments: part.functionCall.args ?? {}, - }, - }; -} + function responseToParts(response: GoogleLLMResponse): GeminiPart[] { + const responseData = responseToGenerateContentResponseData(response); + const parts = responseData?.candidates?.[0]?.content?.parts ?? []; + return parts; + } -export function partsToToolsRaw(parts: GeminiPart[]): ToolCallRaw[] { - return parts - .map((part: GeminiPart) => { - if (part === undefined || part === null) { - return null; - } else if ("functionCall" in part) { - return functionCallPartToToolRaw(part); - } else { - return null; - } - }) - .reduce((acc, content) => { - if (content) { - acc.push(content); - } - return acc; - }, [] as ToolCallRaw[]); -} + function partToText(part: GeminiPart): string { + return "text" in part ? part.text : ""; + } -export function toolsRawToTools(raws: ToolCallRaw[]): ToolCall[] { - return raws.map((raw) => toolRawToTool(raw)); -} + function responseToString(response: GoogleLLMResponse): string { + const parts = responseToParts(response); + const ret: string = parts.reduce((acc, part) => { + const val = partToText(part); + return acc + val; + }, ""); + return ret; + } -export function responseToGenerateContentResponseData( - response: GoogleLLMResponse -): GenerateContentResponseData { - if ("nextChunk" in response.data) { - throw new Error("Cannot convert Stream to GenerateContentResponseData"); - } else if (Array.isArray(response.data)) { - // Collapse the array of response data as if it was a single one - return response.data.reduce( - ( - acc: GenerateContentResponseData, - val: GenerateContentResponseData - ): GenerateContentResponseData => { - // Add all the parts - // FIXME: Handle other candidates? - const valParts = val?.candidates?.[0]?.content?.parts ?? []; - acc.candidates[0].content.parts.push(...valParts); - - // FIXME: Merge promptFeedback and safety settings - acc.promptFeedback = val.promptFeedback; - return acc; + function safeResponseTo( + response: GoogleLLMResponse, + safetyHandler: GoogleAISafetyHandler, + responseTo: (response: GoogleLLMResponse) => RetType + ): RetType { + try { + const safeResponse = safetyHandler.handle(response); + return responseTo(safeResponse); + } catch (xx) { + // eslint-disable-next-line no-instanceof/no-instanceof + if (xx instanceof GoogleAISafetyError) { + const ret = responseTo(xx.response); + xx.reply = ret; } - ); - } else { - return response.data as GenerateContentResponseData; + throw xx; + } } -} -export function responseToParts(response: GoogleLLMResponse): GeminiPart[] { - const responseData = responseToGenerateContentResponseData(response); - const parts = responseData?.candidates?.[0]?.content?.parts ?? []; - return parts; -} - -export function partToText(part: GeminiPart): string { - return "text" in part ? part.text : ""; -} - -export function responseToString(response: GoogleLLMResponse): string { - const parts = responseToParts(response); - const ret: string = parts.reduce((acc, part) => { - const val = partToText(part); - return acc + val; - }, ""); - return ret; -} + function safeResponseToString( + response: GoogleLLMResponse, + safetyHandler: GoogleAISafetyHandler + ): string { + return safeResponseTo(response, safetyHandler, responseToString); + } -function safeResponseTo( - response: GoogleLLMResponse, - safetyHandler: GoogleAISafetyHandler, - responseTo: (response: GoogleLLMResponse) => RetType -): RetType { - try { - const safeResponse = safetyHandler.handle(response); - return responseTo(safeResponse); - } catch (xx) { - // eslint-disable-next-line no-instanceof/no-instanceof - if (xx instanceof GoogleAISafetyError) { - const ret = responseTo(xx.response); - xx.reply = ret; + function responseToGenerationInfo(response: GoogleLLMResponse) { + if (!Array.isArray(response.data)) { + return {}; } - throw xx; + const data = response.data[0]; + return { + usage_metadata: { + prompt_token_count: data.usageMetadata?.promptTokenCount, + candidates_token_count: data.usageMetadata?.candidatesTokenCount, + total_token_count: data.usageMetadata?.totalTokenCount, + }, + safety_ratings: data.candidates[0]?.safetyRatings?.map((rating) => ({ + category: rating.category, + probability: rating.probability, + probability_score: rating.probabilityScore, + severity: rating.severity, + severity_score: rating.severityScore, + })), + finish_reason: data.candidates[0]?.finishReason, + }; } -} - -export function safeResponseToString( - response: GoogleLLMResponse, - safetyHandler: GoogleAISafetyHandler -): string { - return safeResponseTo(response, safetyHandler, responseToString); -} -export function responseToGenerationInfo(response: GoogleLLMResponse) { - if (!Array.isArray(response.data)) { - return {}; + function responseToChatGeneration( + response: GoogleLLMResponse + ): ChatGenerationChunk { + return new ChatGenerationChunk({ + text: responseToString(response), + message: partToMessageChunk(responseToParts(response)[0]), + generationInfo: responseToGenerationInfo(response), + }); } - const data = response.data[0]; - return { - usage_metadata: { - prompt_token_count: data.usageMetadata?.promptTokenCount, - candidates_token_count: data.usageMetadata?.candidatesTokenCount, - total_token_count: data.usageMetadata?.totalTokenCount, - }, - safety_ratings: data.candidates[0]?.safetyRatings?.map((rating) => ({ - category: rating.category, - probability: rating.probability, - probability_score: rating.probabilityScore, - severity: rating.severity, - severity_score: rating.severityScore, - })), - finish_reason: data.candidates[0]?.finishReason, - }; -} - -export function responseToGeneration(response: GoogleLLMResponse): Generation { - return { - text: responseToString(response), - generationInfo: responseToGenerationInfo(response), - }; -} - -export function safeResponseToGeneration( - response: GoogleLLMResponse, - safetyHandler: GoogleAISafetyHandler -): Generation { - return safeResponseTo(response, safetyHandler, responseToGeneration); -} -export function responseToChatGeneration( - response: GoogleLLMResponse -): ChatGenerationChunk { - return new ChatGenerationChunk({ - text: responseToString(response), - message: partToMessageChunk(responseToParts(response)[0]), - generationInfo: responseToGenerationInfo(response), - }); -} - -export function safeResponseToChatGeneration( - response: GoogleLLMResponse, - safetyHandler: GoogleAISafetyHandler -): ChatGenerationChunk { - return safeResponseTo(response, safetyHandler, responseToChatGeneration); -} + function safeResponseToChatGeneration( + response: GoogleLLMResponse, + safetyHandler: GoogleAISafetyHandler + ): ChatGenerationChunk { + return safeResponseTo(response, safetyHandler, responseToChatGeneration); + } -export function chunkToString(chunk: BaseMessageChunk): string { - if (chunk === null) { - return ""; - } else if (typeof chunk.content === "string") { - return chunk.content; - } else if (chunk.content.length === 0) { - return ""; - } else if (chunk.content[0].type === "text") { - return chunk.content[0].text; - } else { - throw new Error(`Unexpected chunk: ${chunk}`); + function chunkToString(chunk: BaseMessageChunk): string { + if (chunk === null) { + return ""; + } else if (typeof chunk.content === "string") { + return chunk.content; + } else if (chunk.content.length === 0) { + return ""; + } else if (chunk.content[0].type === "text") { + return chunk.content[0].text; + } else { + throw new Error(`Unexpected chunk: ${chunk}`); + } } -} -export function partToMessageChunk(part: GeminiPart): BaseMessageChunk { - const fields = partsToBaseMessageChunkFields([part]); - if (typeof fields.content === "string") { + function partToMessageChunk(part: GeminiPart): BaseMessageChunk { + const fields = partsToBaseMessageChunkFields([part]); + if (typeof fields.content === "string") { + return new AIMessageChunk(fields); + } else if (fields.content.every((item) => item.type === "text")) { + const newContent = fields.content + .map((item) => ("text" in item ? item.text : "")) + .join(""); + return new AIMessageChunk({ + ...fields, + content: newContent, + }); + } return new AIMessageChunk(fields); - } else if (fields.content.every((item) => item.type === "text")) { - const newContent = fields.content - .map((item) => ("text" in item ? item.text : "")) - .join(""); - return new AIMessageChunk({ - ...fields, - content: newContent, - }); } - return new AIMessageChunk(fields); -} -export function partToChatGeneration(part: GeminiPart): ChatGeneration { - const message = partToMessageChunk(part); - const text = partToText(part); - return new ChatGenerationChunk({ - text, - message, - }); -} + function partToChatGeneration(part: GeminiPart): ChatGeneration { + const message = partToMessageChunk(part); + const text = partToText(part); + return new ChatGenerationChunk({ + text, + message, + }); + } -export function responseToChatGenerations( - response: GoogleLLMResponse -): ChatGeneration[] { - const parts = responseToParts(response); - let ret = parts.map((part) => partToChatGeneration(part)); - if (ret.every((item) => typeof item.message.content === "string")) { - const combinedContent = ret.map((item) => item.message.content).join(""); - const combinedText = ret.map((item) => item.text).join(""); - const toolCallChunks: ToolCallChunk[] | undefined = ret[ - ret.length - 1 - ]?.message.additional_kwargs?.tool_calls?.map((toolCall, i) => ({ - name: toolCall.function.name, - args: toolCall.function.arguments, - id: toolCall.id, - index: i, - type: "tool_call_chunk", - })); - let usageMetadata: UsageMetadata | undefined; - if ("usageMetadata" in response.data) { - usageMetadata = { - input_tokens: response.data.usageMetadata.promptTokenCount as number, - output_tokens: response.data.usageMetadata - .candidatesTokenCount as number, - total_tokens: response.data.usageMetadata.totalTokenCount as number, - }; - } - ret = [ - new ChatGenerationChunk({ - message: new AIMessageChunk({ - content: combinedContent, - additional_kwargs: ret[ret.length - 1]?.message.additional_kwargs, - tool_call_chunks: toolCallChunks, - usage_metadata: usageMetadata, + function responseToChatGenerations( + response: GoogleLLMResponse + ): ChatGeneration[] { + const parts = responseToParts(response); + let ret = parts.map((part) => partToChatGeneration(part)); + if (ret.every((item) => typeof item.message.content === "string")) { + const combinedContent = ret.map((item) => item.message.content).join(""); + const combinedText = ret.map((item) => item.text).join(""); + const toolCallChunks: ToolCallChunk[] | undefined = ret[ + ret.length - 1 + ]?.message.additional_kwargs?.tool_calls?.map((toolCall, i) => ({ + name: toolCall.function.name, + args: toolCall.function.arguments, + id: toolCall.id, + index: i, + type: "tool_call_chunk", + })); + let usageMetadata: UsageMetadata | undefined; + if ("usageMetadata" in response.data) { + usageMetadata = { + input_tokens: response.data.usageMetadata.promptTokenCount as number, + output_tokens: response.data.usageMetadata + .candidatesTokenCount as number, + total_tokens: response.data.usageMetadata.totalTokenCount as number, + }; + } + ret = [ + new ChatGenerationChunk({ + message: new AIMessageChunk({ + content: combinedContent, + additional_kwargs: ret[ret.length - 1]?.message.additional_kwargs, + tool_call_chunks: toolCallChunks, + usage_metadata: usageMetadata, + }), + text: combinedText, + generationInfo: ret[ret.length - 1].generationInfo, }), - text: combinedText, - generationInfo: ret[ret.length - 1].generationInfo, - }), - ]; + ]; + } + return ret; } - return ret; -} - -export function responseToBaseMessageFields( - response: GoogleLLMResponse -): BaseMessageFields { - const parts = responseToParts(response); - return partsToBaseMessageChunkFields(parts); -} -export function partsToBaseMessageChunkFields( - parts: GeminiPart[] -): AIMessageChunkFields { - const fields: AIMessageChunkFields = { - content: partsToMessageContent(parts), - tool_call_chunks: [], - tool_calls: [], - invalid_tool_calls: [], - }; + function responseToBaseMessageFields( + response: GoogleLLMResponse + ): BaseMessageFields { + const parts = responseToParts(response); + return partsToBaseMessageChunkFields(parts); + } - const rawTools = partsToToolsRaw(parts); - if (rawTools.length > 0) { - const tools = toolsRawToTools(rawTools); - for (const tool of tools) { - fields.tool_call_chunks?.push({ - name: tool.function.name, - args: tool.function.arguments, - id: tool.id, - type: "tool_call_chunk", - }); + function partsToBaseMessageChunkFields( + parts: GeminiPart[] + ): AIMessageChunkFields { + const fields: AIMessageChunkFields = { + content: partsToMessageContent(parts), + tool_call_chunks: [], + tool_calls: [], + invalid_tool_calls: [], + }; - try { - fields.tool_calls?.push({ - name: tool.function.name, - args: JSON.parse(tool.function.arguments), - id: tool.id, - type: "tool_call", - }); - // eslint-disable-next-line @typescript-eslint/no-explicit-any - } catch (e: any) { - fields.invalid_tool_calls?.push({ + const rawTools = partsToToolsRaw(parts); + if (rawTools.length > 0) { + const tools = toolsRawToTools(rawTools); + for (const tool of tools) { + fields.tool_call_chunks?.push({ name: tool.function.name, args: tool.function.arguments, id: tool.id, - error: e.message, - type: "invalid_tool_call", + type: "tool_call_chunk", }); + + try { + fields.tool_calls?.push({ + name: tool.function.name, + args: JSON.parse(tool.function.arguments), + id: tool.id, + }); + // eslint-disable-next-line @typescript-eslint/no-explicit-any + } catch (e: any) { + fields.invalid_tool_calls?.push({ + name: tool.function.name, + args: tool.function.arguments, + id: tool.id, + error: e.message, + type: "invalid_tool_call", + }); + } } + fields.additional_kwargs = { + tool_calls: tools, + }; } - fields.additional_kwargs = { - tool_calls: tools, - }; + return fields; } - return fields; -} -export function responseToBaseMessage( - response: GoogleLLMResponse -): BaseMessage { - const fields = responseToBaseMessageFields(response); - return new AIMessage(fields); -} + function responseToBaseMessage(response: GoogleLLMResponse): BaseMessage { + const fields = responseToBaseMessageFields(response); + return new AIMessage(fields); + } -export function safeResponseToBaseMessage( - response: GoogleLLMResponse, - safetyHandler: GoogleAISafetyHandler -): BaseMessage { - return safeResponseTo(response, safetyHandler, responseToBaseMessage); -} + function safeResponseToBaseMessage( + response: GoogleLLMResponse, + safetyHandler: GoogleAISafetyHandler + ): BaseMessage { + return safeResponseTo(response, safetyHandler, responseToBaseMessage); + } + + function responseToChatResult(response: GoogleLLMResponse): ChatResult { + const generations = responseToChatGenerations(response); + return { + generations, + llmOutput: responseToGenerationInfo(response), + }; + } + + function safeResponseToChatResult( + response: GoogleLLMResponse, + safetyHandler: GoogleAISafetyHandler + ): ChatResult { + return safeResponseTo(response, safetyHandler, responseToChatResult); + } -export function responseToChatResult(response: GoogleLLMResponse): ChatResult { - const generations = responseToChatGenerations(response); return { - generations, - llmOutput: responseToGenerationInfo(response), + messageContentToParts, + baseMessageToContent, + safeResponseToString, + safeResponseToChatGeneration, + chunkToString, + safeResponseToBaseMessage, + safeResponseToChatResult, }; } -export function safeResponseToChatResult( - response: GoogleLLMResponse, - safetyHandler: GoogleAISafetyHandler -): ChatResult { - return safeResponseTo(response, safetyHandler, responseToChatResult); -} - export function validateGeminiParams(params: GoogleAIModelParams): void { if (params.maxOutputTokens && params.maxOutputTokens < 0) { throw new Error("`maxOutputTokens` must be a positive integer"); diff --git a/libs/langchain-google-gauth/src/index.ts b/libs/langchain-google-gauth/src/index.ts index 7f420a4ed6d0..4cd6bd0176ab 100644 --- a/libs/langchain-google-gauth/src/index.ts +++ b/libs/langchain-google-gauth/src/index.ts @@ -1,3 +1,5 @@ export * from "./chat_models.js"; export * from "./llms.js"; export * from "./embeddings.js"; + +export * from "./media.js"; diff --git a/libs/langchain-google-gauth/src/media.ts b/libs/langchain-google-gauth/src/media.ts new file mode 100644 index 000000000000..b44aadd0f858 --- /dev/null +++ b/libs/langchain-google-gauth/src/media.ts @@ -0,0 +1,31 @@ +import { GoogleAbstractedClient } from "@langchain/google-common"; +import { + BlobStoreGoogleCloudStorageBase, + BlobStoreGoogleCloudStorageBaseParams, + BlobStoreAIStudioFileBase, + BlobStoreAIStudioFileBaseParams, +} from "@langchain/google-common/experimental/media"; +import { GoogleAuthOptions } from "google-auth-library"; +import { GAuthClient } from "./auth.js"; + +export interface BlobStoreGoogleCloudStorageParams + extends BlobStoreGoogleCloudStorageBaseParams {} + +export class BlobStoreGoogleCloudStorage extends BlobStoreGoogleCloudStorageBase { + buildClient( + fields?: BlobStoreGoogleCloudStorageParams + ): GoogleAbstractedClient { + return new GAuthClient(fields); + } +} + +export interface BlobStoreAIStudioFileParams + extends BlobStoreAIStudioFileBaseParams {} + +export class BlobStoreAIStudioFile extends BlobStoreAIStudioFileBase { + buildAbstractedClient( + fields?: BlobStoreAIStudioFileParams + ): GoogleAbstractedClient { + return new GAuthClient(fields); + } +} diff --git a/libs/langchain-google-gauth/src/tests/chat_models.int.test.ts b/libs/langchain-google-gauth/src/tests/chat_models.int.test.ts new file mode 100644 index 000000000000..5baa6aaf1973 --- /dev/null +++ b/libs/langchain-google-gauth/src/tests/chat_models.int.test.ts @@ -0,0 +1,315 @@ +import { test } from "@jest/globals"; +import { BaseLanguageModelInput } from "@langchain/core/language_models/base"; +import { ChatPromptValue } from "@langchain/core/prompt_values"; +import { + AIMessage, + AIMessageChunk, + BaseMessage, + BaseMessageChunk, + BaseMessageLike, + HumanMessage, + HumanMessageChunk, + MessageContentComplex, + SystemMessage, + ToolMessage, +} from "@langchain/core/messages"; +import { + BackedBlobStore, + MediaBlob, + MediaManager, + ReadThroughBlobStore, + SimpleWebBlobStore, +} from "@langchain/google-common/experimental/utils/media_core"; +import { GoogleCloudStorageUri } from "@langchain/google-common/experimental/media"; +import { InMemoryStore } from "@langchain/core/stores"; +import { GeminiTool } from "../types.js"; +import { ChatGoogle } from "../chat_models.js"; +import { BlobStoreGoogleCloudStorage } from "../media.js"; + +describe("GAuth Chat", () => { + test("invoke", async () => { + const model = new ChatGoogle(); + try { + const res = await model.invoke("What is 1 + 1?"); + expect(res).toBeDefined(); + expect(res._getType()).toEqual("ai"); + + const aiMessage = res as AIMessageChunk; + expect(aiMessage.content).toBeDefined(); + + expect(typeof aiMessage.content).toBe("string"); + const text = aiMessage.content as string; + expect(text).toMatch(/(1 + 1 (equals|is|=) )?2.? ?/); + + /* + expect(aiMessage.content.length).toBeGreaterThan(0); + expect(aiMessage.content[0]).toBeDefined(); + const content = aiMessage.content[0] as MessageContentComplex; + expect(content).toHaveProperty("type"); + expect(content.type).toEqual("text"); + + const textContent = content as MessageContentText; + expect(textContent.text).toBeDefined(); + expect(textContent.text).toEqual("2"); + */ + } catch (e) { + console.error(e); + throw e; + } + }); + + test("generate", async () => { + const model = new ChatGoogle(); + try { + const messages: BaseMessage[] = [ + new SystemMessage( + "You will reply to all requests to flip a coin with either H, indicating heads, or T, indicating tails." + ), + new HumanMessage("Flip it"), + new AIMessage("T"), + new HumanMessage("Flip the coin again"), + ]; + const res = await model.predictMessages(messages); + expect(res).toBeDefined(); + expect(res._getType()).toEqual("ai"); + + const aiMessage = res as AIMessageChunk; + expect(aiMessage.content).toBeDefined(); + + expect(typeof aiMessage.content).toBe("string"); + const text = aiMessage.content as string; + expect(["H", "T"]).toContainEqual(text); + + /* + expect(aiMessage.content.length).toBeGreaterThan(0); + expect(aiMessage.content[0]).toBeDefined(); + + const content = aiMessage.content[0] as MessageContentComplex; + expect(content).toHaveProperty("type"); + expect(content.type).toEqual("text"); + + const textContent = content as MessageContentText; + expect(textContent.text).toBeDefined(); + expect(["H", "T"]).toContainEqual(textContent.text); + */ + } catch (e) { + console.error(e); + throw e; + } + }); + + test("stream", async () => { + const model = new ChatGoogle(); + try { + const input: BaseLanguageModelInput = new ChatPromptValue([ + new SystemMessage( + "You will reply to all requests to flip a coin with either H, indicating heads, or T, indicating tails." + ), + new HumanMessage("Flip it"), + new AIMessage("T"), + new HumanMessage("Flip the coin again"), + ]); + const res = await model.stream(input); + const resArray: BaseMessageChunk[] = []; + for await (const chunk of res) { + resArray.push(chunk); + } + expect(resArray).toBeDefined(); + expect(resArray.length).toBeGreaterThanOrEqual(1); + + const lastChunk = resArray[resArray.length - 1]; + expect(lastChunk).toBeDefined(); + expect(lastChunk._getType()).toEqual("ai"); + const aiChunk = lastChunk as AIMessageChunk; + console.log(aiChunk); + + console.log(JSON.stringify(resArray, null, 2)); + } catch (e) { + console.error(e); + throw e; + } + }); + + test("function", async () => { + const tools: GeminiTool[] = [ + { + functionDeclarations: [ + { + name: "test", + description: + "Run a test with a specific name and get if it passed or failed", + parameters: { + type: "object", + properties: { + testName: { + type: "string", + description: "The name of the test that should be run.", + }, + }, + required: ["testName"], + }, + }, + ], + }, + ]; + const model = new ChatGoogle().bind({ tools }); + const result = await model.invoke("Run a test on the cobalt project"); + expect(result).toHaveProperty("content"); + expect(result.content).toBe(""); + const args = result?.lc_kwargs?.additional_kwargs; + expect(args).toBeDefined(); + expect(args).toHaveProperty("tool_calls"); + expect(Array.isArray(args.tool_calls)).toBeTruthy(); + expect(args.tool_calls).toHaveLength(1); + const call = args.tool_calls[0]; + expect(call).toHaveProperty("type"); + expect(call.type).toBe("function"); + expect(call).toHaveProperty("function"); + const func = call.function; + expect(func).toBeDefined(); + expect(func).toHaveProperty("name"); + expect(func.name).toBe("test"); + expect(func).toHaveProperty("arguments"); + expect(typeof func.arguments).toBe("string"); + expect(func.arguments.replaceAll("\n", "")).toBe('{"testName":"cobalt"}'); + }); + + test("function reply", async () => { + const tools: GeminiTool[] = [ + { + functionDeclarations: [ + { + name: "test", + description: + "Run a test with a specific name and get if it passed or failed", + parameters: { + type: "object", + properties: { + testName: { + type: "string", + description: "The name of the test that should be run.", + }, + }, + required: ["testName"], + }, + }, + ], + }, + ]; + const model = new ChatGoogle().bind({ tools }); + const toolResult = { + testPassed: true, + }; + const messages: BaseMessageLike[] = [ + new HumanMessage("Run a test on the cobalt project."), + new AIMessage("", { + tool_calls: [ + { + id: "test", + type: "function", + function: { + name: "test", + arguments: '{"testName":"cobalt"}', + }, + }, + ], + }), + new ToolMessage(JSON.stringify(toolResult), "test"), + ]; + const res = await model.stream(messages); + const resArray: BaseMessageChunk[] = []; + for await (const chunk of res) { + resArray.push(chunk); + } + console.log(JSON.stringify(resArray, null, 2)); + }); + + test("withStructuredOutput", async () => { + const tool = { + name: "get_weather", + description: + "Get the weather of a specific location and return the temperature in Celsius.", + parameters: { + type: "object", + properties: { + location: { + type: "string", + description: "The name of city to get the weather for.", + }, + }, + required: ["location"], + }, + }; + const model = new ChatGoogle().withStructuredOutput(tool); + const result = await model.invoke("What is the weather in Paris?"); + expect(result).toHaveProperty("location"); + }); + + test("media - fileData", async () => { + class MemStore extends InMemoryStore { + get length() { + return Object.keys(this.store).length; + } + } + const aliasMemory = new MemStore(); + const aliasStore = new BackedBlobStore({ + backingStore: aliasMemory, + defaultFetchOptions: { + actionIfBlobMissing: undefined, + }, + }); + const canonicalStore = new BlobStoreGoogleCloudStorage({ + uriPrefix: new GoogleCloudStorageUri("gs://test-langchainjs/mediatest/"), + defaultStoreOptions: { + actionIfInvalid: "prefixPath", + }, + }); + const blobStore = new ReadThroughBlobStore({ + baseStore: aliasStore, + backingStore: canonicalStore, + }); + const resolver = new SimpleWebBlobStore(); + const mediaManager = new MediaManager({ + store: blobStore, + resolvers: [resolver], + }); + const model = new ChatGoogle({ + modelName: "gemini-1.5-flash", + mediaManager, + }); + + const message: MessageContentComplex[] = [ + { + type: "text", + text: "What is in this image?", + }, + { + type: "media", + fileUri: "https://js.langchain.com/v0.2/img/brand/wordmark.png", + }, + ]; + + const messages: BaseMessage[] = [ + new HumanMessageChunk({ content: message }), + ]; + + try { + const res = await model.invoke(messages); + + console.log(res); + + expect(res).toBeDefined(); + expect(res._getType()).toEqual("ai"); + + const aiMessage = res as AIMessageChunk; + expect(aiMessage.content).toBeDefined(); + + expect(typeof aiMessage.content).toBe("string"); + const text = aiMessage.content as string; + expect(text).toMatch(/LangChain/); + } catch (e) { + console.error(e); + throw e; + } + }); +}); diff --git a/libs/langchain-google-gauth/src/tests/data/blue-square.png b/libs/langchain-google-gauth/src/tests/data/blue-square.png new file mode 100644 index 0000000000000000000000000000000000000000..c64355dc335b834aaa2a97d25ed2c02195cb8400 GIT binary patch literal 176 zcmeAS@N?(olHy`uVBq!ia0vp^AT}2V6Od#Ih`sf3npoC79rQ=8##bNvY8S|xv6<2KrRD=b5UwyNotBhd1gt5g1e`0K#E=} zJ5XHB)5S4F;&Sqz|Nrfo^9~$o5Z~aSvA#C`g330_j{?_hCtT_g`6 { + test("save text no-metadata", async () => { + const uriPrefix = new GoogleCloudStorageUri("gs://test-langchainjs/"); + const uri = `gs://test-langchainjs/text/test-${Date.now()}-nm`; + const content = "This is a test"; + const blob = await MediaBlob.fromBlob( + new Blob([content], { type: "text/plain" }), + { + path: uri, + } + ); + const config: BlobStoreGoogleCloudStorageParams = { + uriPrefix, + }; + const blobStore = new BlobStoreGoogleCloudStorage(config); + const storedBlob = await blobStore.store(blob); + // console.log(storedBlob); + expect(storedBlob?.path).toEqual(uri); + expect(await storedBlob?.asString()).toEqual(content); + expect(storedBlob?.mimetype).toEqual("text/plain"); + expect(storedBlob?.metadata).not.toHaveProperty("metadata"); + expect(storedBlob?.size).toEqual(content.length); + expect(storedBlob?.metadata?.kind).toEqual("storage#object"); + }); + + test("save text with-metadata", async () => { + const uriPrefix = new GoogleCloudStorageUri("gs://test-langchainjs/"); + const uri = `gs://test-langchainjs/text/test-${Date.now()}-wm`; + const content = "This is a test"; + const blob = await MediaBlob.fromBlob( + new Blob([content], { type: "text/plain" }), + { + path: uri, + metadata: { + alpha: "one", + bravo: "two", + }, + } + ); + const config: BlobStoreGoogleCloudStorageParams = { + uriPrefix, + }; + const blobStore = new BlobStoreGoogleCloudStorage(config); + const storedBlob = await blobStore.store(blob); + // console.log(storedBlob); + expect(storedBlob?.path).toEqual(uri); + expect(await storedBlob?.asString()).toEqual(content); + expect(storedBlob?.mimetype).toEqual("text/plain"); + expect(storedBlob?.metadata).toHaveProperty("metadata"); + expect(storedBlob?.metadata?.metadata?.alpha).toEqual("one"); + expect(storedBlob?.metadata?.metadata?.bravo).toEqual("two"); + expect(storedBlob?.size).toEqual(content.length); + expect(storedBlob?.metadata?.kind).toEqual("storage#object"); + }); + + test("save image no-metadata", async () => { + const filename = `src/tests/data/blue-square.png`; + const dataBuffer = await fs.readFile(filename); + const data = new Blob([dataBuffer], { type: "image/png" }); + + const uriPrefix = new GoogleCloudStorageUri("gs://test-langchainjs/"); + const uri = `gs://test-langchainjs/image/test-${Date.now()}-nm`; + const blob = await MediaBlob.fromBlob(data, { + path: uri, + }); + const config: BlobStoreGoogleCloudStorageParams = { + uriPrefix, + }; + const blobStore = new BlobStoreGoogleCloudStorage(config); + const storedBlob = await blobStore.store(blob); + // console.log(storedBlob); + expect(storedBlob?.path).toEqual(uri); + expect(storedBlob?.size).toEqual(176); + expect(storedBlob?.mimetype).toEqual("image/png"); + expect(storedBlob?.metadata?.kind).toEqual("storage#object"); + }); + + test("get text no-metadata", async () => { + const uriPrefix = new GoogleCloudStorageUri("gs://test-langchainjs/"); + const uri: string = "gs://test-langchainjs/text/test-nm"; + const config: BlobStoreGoogleCloudStorageParams = { + uriPrefix, + }; + const blobStore = new BlobStoreGoogleCloudStorage(config); + const blob = await blobStore.fetch(uri); + // console.log(blob); + expect(blob?.path).toEqual(uri); + expect(await blob?.asString()).toEqual("This is a test"); + expect(blob?.mimetype).toEqual("text/plain"); + expect(blob?.metadata).not.toHaveProperty("metadata"); + expect(blob?.size).toEqual(14); + expect(blob?.metadata?.kind).toEqual("storage#object"); + }); + + test("get text with-metadata", async () => { + const uriPrefix = new GoogleCloudStorageUri("gs://test-langchainjs/"); + const uri: string = "gs://test-langchainjs/text/test-wm"; + const config: BlobStoreGoogleCloudStorageParams = { + uriPrefix, + }; + const blobStore = new BlobStoreGoogleCloudStorage(config); + const blob = await blobStore.fetch(uri); + // console.log(blob); + expect(blob?.path).toEqual(uri); + expect(await blob?.asString()).toEqual("This is a test"); + expect(blob?.mimetype).toEqual("text/plain"); + expect(blob?.metadata).toHaveProperty("metadata"); + expect(blob?.metadata?.metadata?.alpha).toEqual("one"); + expect(blob?.metadata?.metadata?.bravo).toEqual("two"); + expect(blob?.size).toEqual(14); + expect(blob?.metadata?.kind).toEqual("storage#object"); + }); + + test("get image no-metadata", async () => { + const uriPrefix = new GoogleCloudStorageUri("gs://test-langchainjs/"); + const uri: string = "gs://test-langchainjs/image/test-nm"; + const config: BlobStoreGoogleCloudStorageParams = { + uriPrefix, + }; + const blobStore = new BlobStoreGoogleCloudStorage(config); + const blob = await blobStore.fetch(uri); + // console.log(storedBlob); + expect(blob?.path).toEqual(uri); + expect(blob?.size).toEqual(176); + expect(blob?.mimetype).toEqual("image/png"); + expect(blob?.metadata?.kind).toEqual("storage#object"); + }); +}); diff --git a/libs/langchain-google-vertexai/src/tests/chat_models.int.test.ts b/libs/langchain-google-vertexai/src/tests/chat_models.int.test.ts index a5b9b1001218..8eeed770fee2 100644 --- a/libs/langchain-google-vertexai/src/tests/chat_models.int.test.ts +++ b/libs/langchain-google-vertexai/src/tests/chat_models.int.test.ts @@ -9,16 +9,31 @@ import { BaseMessageChunk, BaseMessageLike, HumanMessage, + HumanMessageChunk, + MessageContentComplex, SystemMessage, ToolMessage, } from "@langchain/core/messages"; +import { + BlobStoreGoogleCloudStorage, + ChatGoogle, +} from "@langchain/google-gauth"; import { tool } from "@langchain/core/tools"; import { z } from "zod"; import { concat } from "@langchain/core/utils/stream"; +import { + BackedBlobStore, + MediaBlob, + MediaManager, + ReadThroughBlobStore, + SimpleWebBlobStore, +} from "@langchain/google-common/experimental/utils/media_core"; +import { GoogleCloudStorageUri } from "@langchain/google-common/experimental/media"; import { ChatPromptTemplate, MessagesPlaceholder, } from "@langchain/core/prompts"; +import { InMemoryStore } from "@langchain/core/stores"; import { GeminiTool } from "../types.js"; import { ChatVertexAI } from "../chat_models.js"; @@ -196,6 +211,74 @@ describe("GAuth Chat", () => { const result = await model.invoke("What is the weather in Paris?"); expect(result).toHaveProperty("location"); }); + + test("media - fileData", async () => { + class MemStore extends InMemoryStore { + get length() { + return Object.keys(this.store).length; + } + } + const aliasMemory = new MemStore(); + const aliasStore = new BackedBlobStore({ + backingStore: aliasMemory, + defaultFetchOptions: { + actionIfBlobMissing: undefined, + }, + }); + const canonicalStore = new BlobStoreGoogleCloudStorage({ + uriPrefix: new GoogleCloudStorageUri("gs://test-langchainjs/mediatest/"), + defaultStoreOptions: { + actionIfInvalid: "prefixPath", + }, + }); + const blobStore = new ReadThroughBlobStore({ + baseStore: aliasStore, + backingStore: canonicalStore, + }); + const resolver = new SimpleWebBlobStore(); + const mediaManager = new MediaManager({ + store: blobStore, + resolvers: [resolver], + }); + const model = new ChatGoogle({ + modelName: "gemini-1.5-flash", + mediaManager, + }); + + const message: MessageContentComplex[] = [ + { + type: "text", + text: "What is in this image?", + }, + { + type: "media", + fileUri: "https://js.langchain.com/v0.2/img/brand/wordmark.png", + }, + ]; + + const messages: BaseMessage[] = [ + new HumanMessageChunk({ content: message }), + ]; + + try { + const res = await model.invoke(messages); + + console.log(res); + + expect(res).toBeDefined(); + expect(res._getType()).toEqual("ai"); + + const aiMessage = res as AIMessageChunk; + expect(aiMessage.content).toBeDefined(); + + expect(typeof aiMessage.content).toBe("string"); + const text = aiMessage.content as string; + expect(text).toMatch(/LangChain/); + } catch (e) { + console.error(e); + throw e; + } + }); }); test("Stream token count usage_metadata", async () => { diff --git a/libs/langchain-google-webauth/package.json b/libs/langchain-google-webauth/package.json index 320f731a088b..a6830b7eac5d 100644 --- a/libs/langchain-google-webauth/package.json +++ b/libs/langchain-google-webauth/package.json @@ -58,7 +58,8 @@ "release-it": "^17.6.0", "rollup": "^4.5.2", "ts-jest": "^29.1.0", - "typescript": "<5.2.0" + "typescript": "<5.2.0", + "zod": "^3.23.8" }, "publishConfig": { "access": "public" diff --git a/libs/langchain-google-webauth/src/media.ts b/libs/langchain-google-webauth/src/media.ts new file mode 100644 index 000000000000..4b631b519d31 --- /dev/null +++ b/libs/langchain-google-webauth/src/media.ts @@ -0,0 +1,33 @@ +import { + GoogleAbstractedClient, + GoogleBaseLLMInput, +} from "@langchain/google-common"; +import { + BlobStoreAIStudioFileBase, + BlobStoreAIStudioFileBaseParams, + BlobStoreGoogleCloudStorageBase, + BlobStoreGoogleCloudStorageBaseParams, +} from "@langchain/google-common/experimental/media"; +import { WebGoogleAuth, WebGoogleAuthOptions } from "./auth.js"; + +export interface BlobStoreGoogleCloudStorageParams + extends BlobStoreGoogleCloudStorageBaseParams {} + +export class BlobStoreGoogleCloudStorage extends BlobStoreGoogleCloudStorageBase { + buildClient( + fields?: GoogleBaseLLMInput + ): GoogleAbstractedClient { + return new WebGoogleAuth(fields); + } +} + +export interface BlobStoreAIStudioFileParams + extends BlobStoreAIStudioFileBaseParams {} + +export class BlobStoreAIStudioFile extends BlobStoreAIStudioFileBase { + buildAbstractedClient( + fields?: BlobStoreAIStudioFileParams + ): GoogleAbstractedClient { + return new WebGoogleAuth(fields); + } +} diff --git a/libs/langchain-google-webauth/src/tests/chat_models.int.test.ts b/libs/langchain-google-webauth/src/tests/chat_models.int.test.ts new file mode 100644 index 000000000000..60fcba032d18 --- /dev/null +++ b/libs/langchain-google-webauth/src/tests/chat_models.int.test.ts @@ -0,0 +1,247 @@ +/* eslint-disable import/no-extraneous-dependencies */ +import { StructuredTool } from "@langchain/core/tools"; +import { z } from "zod"; +import { test } from "@jest/globals"; +import { + AIMessage, + AIMessageChunk, + BaseMessage, + BaseMessageChunk, + HumanMessage, + HumanMessageChunk, + MessageContentComplex, + SystemMessage, + ToolMessage, +} from "@langchain/core/messages"; +import { BaseLanguageModelInput } from "@langchain/core/language_models/base"; +import { ChatPromptValue } from "@langchain/core/prompt_values"; +import { + MediaManager, + SimpleWebBlobStore, +} from "@langchain/google-common/experimental/utils/media_core"; +import { ChatGoogle } from "../chat_models.js"; +import { BlobStoreAIStudioFile } from "../media.js"; + +class WeatherTool extends StructuredTool { + schema = z.object({ + locations: z + .array(z.object({ name: z.string() })) + .describe("The name of cities to get the weather for."), + }); + + description = + "Get the weather of a specific location and return the temperature in Celsius."; + + name = "get_weather"; + + async _call(input: z.infer) { + console.log(`WeatherTool called with input: ${input}`); + return `The weather in ${JSON.stringify(input.locations)} is 25°C`; + } +} + +describe("Google APIKey Chat", () => { + test("invoke", async () => { + const model = new ChatGoogle(); + try { + const res = await model.invoke("What is 1 + 1?"); + console.log(res); + expect(res).toBeDefined(); + expect(res._getType()).toEqual("ai"); + + const aiMessage = res as AIMessageChunk; + console.log(aiMessage); + expect(aiMessage.content).toBeDefined(); + expect(aiMessage.content.length).toBeGreaterThan(0); + expect(aiMessage.content[0]).toBeDefined(); + + // const content = aiMessage.content[0] as MessageContentComplex; + // expect(content).toHaveProperty("type"); + // expect(content.type).toEqual("text"); + + // const textContent = content as MessageContentText; + // expect(textContent.text).toBeDefined(); + // expect(textContent.text).toEqual("2"); + } catch (e) { + console.error(e); + throw e; + } + }); + + test("generate", async () => { + const model = new ChatGoogle(); + try { + const messages: BaseMessage[] = [ + new SystemMessage( + "You will reply to all requests to flip a coin with either H, indicating heads, or T, indicating tails." + ), + new HumanMessage("Flip it"), + new AIMessage("T"), + new HumanMessage("Flip the coin again"), + ]; + const res = await model.predictMessages(messages); + expect(res).toBeDefined(); + expect(res._getType()).toEqual("ai"); + + const aiMessage = res as AIMessageChunk; + expect(aiMessage.content).toBeDefined(); + expect(aiMessage.content.length).toBeGreaterThan(0); + expect(aiMessage.content[0]).toBeDefined(); + console.log(aiMessage); + + // const content = aiMessage.content[0] as MessageContentComplex; + // expect(content).toHaveProperty("type"); + // expect(content.type).toEqual("text"); + + // const textContent = content as MessageContentText; + // expect(textContent.text).toBeDefined(); + // expect(["H", "T"]).toContainEqual(textContent.text); + } catch (e) { + console.error(e); + throw e; + } + }); + + test("stream", async () => { + const model = new ChatGoogle(); + try { + const input: BaseLanguageModelInput = new ChatPromptValue([ + new SystemMessage( + "You will reply to all requests to flip a coin with either H, indicating heads, or T, indicating tails." + ), + new HumanMessage("Flip it"), + new AIMessage("T"), + new HumanMessage("Flip the coin again"), + ]); + const res = await model.stream(input); + const resArray: BaseMessageChunk[] = []; + for await (const chunk of res) { + resArray.push(chunk); + } + expect(resArray).toBeDefined(); + expect(resArray.length).toBeGreaterThanOrEqual(1); + + const lastChunk = resArray[resArray.length - 1]; + expect(lastChunk).toBeDefined(); + expect(lastChunk._getType()).toEqual("ai"); + const aiChunk = lastChunk as AIMessageChunk; + console.log(aiChunk); + + console.log(JSON.stringify(resArray, null, 2)); + } catch (e) { + console.error(e); + throw e; + } + }); + + test.skip("Tool call", async () => { + const chat = new ChatGoogle().bindTools([new WeatherTool()]); + const res = await chat.invoke("What is the weather in SF and LA"); + console.log(res); + expect(res.tool_calls?.length).toEqual(1); + expect(res.tool_calls?.[0].args).toEqual( + JSON.parse(res.additional_kwargs.tool_calls?.[0].function.arguments ?? "") + ); + }); + + test.skip("Few shotting with tool calls", async () => { + const chat = new ChatGoogle().bindTools([new WeatherTool()]); + const res = await chat.invoke("What is the weather in SF"); + console.log(res); + const res2 = await chat.invoke([ + new HumanMessage("What is the weather in SF?"), + new AIMessage({ + content: "", + tool_calls: [ + { + id: "12345", + name: "get_current_weather", + args: { + location: "SF", + }, + }, + ], + }), + new ToolMessage({ + tool_call_id: "12345", + content: "It is currently 24 degrees with hail in SF.", + }), + new AIMessage("It is currently 24 degrees in SF with hail in SF."), + new HumanMessage("What did you say the weather was?"), + ]); + console.log(res2); + expect(res2.content).toContain("24"); + }); + + test.skip("withStructuredOutput", async () => { + const tool = { + name: "get_weather", + description: + "Get the weather of a specific location and return the temperature in Celsius.", + parameters: { + type: "object", + properties: { + location: { + type: "string", + description: "The name of city to get the weather for.", + }, + }, + required: ["location"], + }, + }; + const model = new ChatGoogle().withStructuredOutput(tool); + const result = await model.invoke("What is the weather in Paris?"); + expect(result).toHaveProperty("location"); + }); + + test("media - fileData", async () => { + const canonicalStore = new BlobStoreAIStudioFile({}); + const resolver = new SimpleWebBlobStore(); + const mediaManager = new MediaManager({ + store: canonicalStore, + resolvers: [resolver], + }); + const model = new ChatGoogle({ + modelName: "gemini-1.5-flash", + apiVersion: "v1beta", + mediaManager, + }); + + const message: MessageContentComplex[] = [ + { + type: "text", + text: "What is in this image?", + }, + { + type: "media", + fileUri: "https://js.langchain.com/v0.2/img/brand/wordmark.png", + }, + ]; + + const messages: BaseMessage[] = [ + new HumanMessageChunk({ content: message }), + ]; + + try { + const res = await model.invoke(messages); + + // console.log(res); + + expect(res).toBeDefined(); + expect(res._getType()).toEqual("ai"); + + const aiMessage = res as AIMessageChunk; + expect(aiMessage.content).toBeDefined(); + + expect(typeof aiMessage.content).toBe("string"); + const text = aiMessage.content as string; + expect(text).toMatch(/LangChain/); + + // eslint-disable-next-line @typescript-eslint/no-explicit-any + } catch (e: any) { + console.error(e); + console.error(JSON.stringify(e.details, null, 1)); + throw e; + } + }); +}); diff --git a/libs/langchain-google-webauth/src/tests/data/blue-square.png b/libs/langchain-google-webauth/src/tests/data/blue-square.png new file mode 100644 index 0000000000000000000000000000000000000000..c64355dc335b834aaa2a97d25ed2c02195cb8400 GIT binary patch literal 176 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ª?<6!C5X;px>3mn_Tte` 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 z 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

Y=oMoh&vBXrT*I0V*)?h?TB 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

($(1kXs;4A(Q|^N*2_1x$UZLO$Y-i5y@+Tr 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