Skip to content
Permalink

Comparing changes

Choose two branches to see what’s changed or to start a new pull request. If you need to, you can also or learn more about diff comparisons.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also . Learn more about diff comparisons here.
base repository: langchain-ai/langchainjs
Failed to load repositories. Confirm that selected base ref is valid, then try again.
Loading
base: 1bf36c906e1bcdb4ea2739f50855db4bf44baa7e
Choose a base ref
...
head repository: langchain-ai/langchainjs
Failed to load repositories. Confirm that selected head ref is valid, then try again.
Loading
compare: 8b06f7f7276678d939db67400ec1dd78454f885c
Choose a head ref
  • 7 commits
  • 14 files changed
  • 3 contributors

Commits on Feb 27, 2025

  1. release(core): 0.3.41 (#7774)

    jacoblee93 authored Feb 27, 2025

    Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
    Copy the full SHA
    d07f6da View commit details
  2. feat(community): Support google cloud storage document loader (#7740)

    Co-authored-by: jacoblee93 <jacoblee93@gmail.com>
    AllenFang and jacoblee93 authored Feb 27, 2025

    Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
    Copy the full SHA
    ed1d8c2 View commit details
  3. release(community): 0.3.33 (#7775)

    jacoblee93 authored Feb 27, 2025

    Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
    Copy the full SHA
    4a645ba View commit details
  4. feat(aws): support reasoning blocks for claude 3.7 (#7768)

    benjamincburns authored Feb 27, 2025
    Copy the full SHA
    b593473 View commit details

Commits on Feb 28, 2025

  1. release(aws): 0.1.5

    benjamincburns committed Feb 28, 2025
    Copy the full SHA
    2de36ac View commit details
  2. release(aws): 0.1.5 (#7777)

    benjamincburns authored Feb 28, 2025
    Copy the full SHA
    f559c04 View commit details
  3. fix(core): Adds fallback for context vars (#7776)

    jacoblee93 authored Feb 28, 2025
    Copy the full SHA
    8b06f7f View commit details
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
---
hide_table_of_contents: true
sidebar_class_name: node-only
---

# Google Cloud Storage

:::tip Compatibility
Only available on Node.js.
:::

This covers how to load a Google Cloud Storage File into LangChain documents.

## Setup

To use this loader, you'll need to have Unstructured already set up and ready to use at an available URL endpoint. It can also be configured to run locally.

See the docs [here](/docs/integrations/document_loaders/file_loaders/unstructured) for information on how to do that.

You'll also need to install the official Google Cloud Storage SDK:

```bash npm2yarn
npm install @langchain/community @langchain/core @google-cloud/storage
```

## Usage

Once Unstructured is configured, you can use the Google Cloud Storage loader to load files and then convert them into a Document.

import CodeBlock from "@theme/CodeBlock";
import Example from "@examples/document_loaders/google_cloud_storage.ts";

<CodeBlock language="typescript">{Example}</CodeBlock>
17 changes: 17 additions & 0 deletions examples/src/document_loaders/google_cloud_storage.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { GoogleCloudStorageLoader } from "@langchain/community/document_loaders/web/google_cloud_storage";

const loader = new GoogleCloudStorageLoader({
bucket: "my-bucket-123",
file: "path/to/file.pdf",
storageOptions: {
keyFilename: "/path/to/keyfile.json",
},
unstructuredLoaderOptions: {
apiUrl: "http://localhost:8000/general/v0/general",
apiKey: "", // this will be soon required
},
});

const docs = await loader.load();

console.log(docs);
2 changes: 1 addition & 1 deletion langchain-core/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@langchain/core",
"version": "0.3.40",
"version": "0.3.41",
"description": "Core LangChain.js abstractions and schemas",
"type": "module",
"engines": {
3 changes: 3 additions & 0 deletions langchain-core/src/singletons/async_local_storage/index.ts
Original file line number Diff line number Diff line change
@@ -78,6 +78,9 @@ class AsyncLocalStorageProvider {
previousValue !== undefined &&
previousValue[_CONTEXT_VARIABLES_KEY] !== undefined
) {
if (runTree === undefined) {
runTree = {};
}
(runTree as any)[_CONTEXT_VARIABLES_KEY] =
previousValue[_CONTEXT_VARIABLES_KEY];
}
12 changes: 6 additions & 6 deletions libs/langchain-aws/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@langchain/aws",
"version": "0.1.4",
"version": "0.1.5",
"description": "LangChain AWS integration",
"type": "module",
"engines": {
@@ -32,15 +32,15 @@
"author": "LangChain",
"license": "MIT",
"dependencies": {
"@aws-sdk/client-bedrock-agent-runtime": "^3.749.0",
"@aws-sdk/client-bedrock-runtime": "^3.749.0",
"@aws-sdk/client-kendra": "^3.749.0",
"@aws-sdk/credential-provider-node": "^3.749.0",
"@aws-sdk/client-bedrock-agent-runtime": "^3.755.0",
"@aws-sdk/client-bedrock-runtime": "^3.755.0",
"@aws-sdk/client-kendra": "^3.750.0",
"@aws-sdk/credential-provider-node": "^3.750.0",
"zod": "^3.23.8",
"zod-to-json-schema": "^3.22.5"
},
"peerDependencies": {
"@langchain/core": ">=0.2.21 <0.4.0"
"@langchain/core": ">=0.3.41 <0.4.0"
},
"devDependencies": {
"@aws-sdk/types": "^3.734.0",
225 changes: 209 additions & 16 deletions libs/langchain-aws/src/common.ts
Original file line number Diff line number Diff line change
@@ -20,12 +20,22 @@ import type {
ContentBlockDeltaEvent,
ConverseStreamMetadataEvent,
ContentBlockStartEvent,
ReasoningContentBlock,
ReasoningContentBlockDelta,
ReasoningTextBlock,
} from "@aws-sdk/client-bedrock-runtime";
import type { DocumentType as __DocumentType } from "@smithy/types";
import { isLangChainTool } from "@langchain/core/utils/function_calling";
import { zodToJsonSchema } from "zod-to-json-schema";
import { ChatGenerationChunk } from "@langchain/core/outputs";
import { ChatBedrockConverseToolType, BedrockToolChoice } from "./types.js";
import {
ChatBedrockConverseToolType,
BedrockToolChoice,
MessageContentReasoningBlock,
MessageContentReasoningBlockReasoningText,
MessageContentReasoningBlockReasoningTextPartial,
MessageContentReasoningBlockRedacted,
} from "./types.js";

export function extractImageInfo(base64: string): ContentBlock.ImageMember {
// Extract the format from the base64 string
@@ -99,22 +109,34 @@ export function convertToConverseMessages(messages: BaseMessage[]): {
text: castMsg.content,
});
} else if (Array.isArray(castMsg.content)) {
const contentBlocks: ContentBlock[] = castMsg.content.map((block) => {
if (block.type === "text" && block.text !== "") {
return {
text: block.text,
};
} else {
const blockValues = Object.fromEntries(
Object.entries(block).filter(([key]) => key !== "type")
);
throw new Error(
`Unsupported content block type: ${
block.type
} with content of ${JSON.stringify(blockValues, null, 2)}`
);
const concatenatedBlocks = concatenateLangchainReasoningBlocks(
castMsg.content
);
const contentBlocks: ContentBlock[] = concatenatedBlocks.map(
(block) => {
if (block.type === "text" && block.text !== "") {
return {
text: block.text,
};
} else if (block.type === "reasoning_content") {
return {
reasoningContent:
langchainReasoningBlockToBedrockReasoningBlock(
block as MessageContentReasoningBlock
),
};
} else {
const blockValues = Object.fromEntries(
Object.entries(block).filter(([key]) => key !== "type")
);
throw new Error(
`Unsupported content block type: ${
block.type
} with content of ${JSON.stringify(blockValues, null, 2)}`
);
}
}
});
);

assistantMsg.content = [
...(assistantMsg.content ? assistantMsg.content : []),
@@ -411,6 +433,12 @@ export function convertConverseMessageToLangChainMessage(
});
} else if ("text" in c && typeof c.text === "string") {
content.push({ type: "text", text: c.text });
} else if ("reasoningContent" in c) {
content.push(
bedrockReasoningBlockToLangchainReasoningBlock(
c.reasoningContent as ReasoningContentBlock
)
);
} else {
content.push(c);
}
@@ -453,6 +481,17 @@ export function handleConverseStreamContentBlockDelta(
],
}),
});
} else if (contentBlockDelta.delta.reasoningContent) {
return new ChatGenerationChunk({
text: "",
message: new AIMessageChunk({
content: [
bedrockReasoningDeltaToLangchainPartialReasoningBlock(
contentBlockDelta.delta.reasoningContent
),
],
}),
});
} else {
throw new Error(
`Unsupported content block type(s): ${JSON.stringify(
@@ -512,3 +551,157 @@ export function handleConverseStreamMetadata(
}),
});
}

export function bedrockReasoningDeltaToLangchainPartialReasoningBlock(
reasoningContent: ReasoningContentBlockDelta
):
| MessageContentReasoningBlockReasoningTextPartial
| MessageContentReasoningBlockRedacted {
const { text, redactedContent, signature } = reasoningContent;
if (text) {
return {
type: "reasoning_content",
reasoningText: { text },
};
}
if (signature) {
return {
type: "reasoning_content",
reasoningText: { signature },
};
}
if (redactedContent) {
return {
type: "reasoning_content",
redactedContent: Buffer.from(redactedContent).toString("base64"),
};
}
throw new Error("Invalid reasoning content");
}

export function bedrockReasoningBlockToLangchainReasoningBlock(
reasoningContent: ReasoningContentBlock
): MessageContentReasoningBlock {
const { reasoningText, redactedContent } = reasoningContent;
if (reasoningText) {
return {
type: "reasoning_content",
reasoningText: reasoningText as Required<ReasoningTextBlock>,
};
}

if (redactedContent) {
return {
type: "reasoning_content",
redactedContent: Buffer.from(redactedContent).toString("base64"),
};
}
throw new Error("Invalid reasoning content");
}

export function langchainReasoningBlockToBedrockReasoningBlock(
content: MessageContentReasoningBlock
): ReasoningContentBlock {
if (content.type !== "reasoning_content") {
throw new Error("Invalid reasoning content");
}
if ("reasoningText" in content) {
return {
reasoningText: content.reasoningText as ReasoningTextBlock,
};
}
if ("redactedContent" in content) {
return {
redactedContent: Buffer.from(content.redactedContent, "base64"),
};
}
throw new Error("Invalid reasoning content");
}

export function concatenateLangchainReasoningBlocks(
content: Array<MessageContentComplex | MessageContentReasoningBlock>
): MessageContentComplex[] {
const concatenatedBlocks: MessageContentComplex[] = [];
let concatenatedBlock: Partial<MessageContentReasoningBlock> = {};

for (const block of content) {
if (block.type !== "reasoning_content") {
// if it's some other block type, end the current block, but keep it so we preserve order
if (Object.keys(concatenatedBlock).length > 0) {
concatenatedBlocks.push(
concatenatedBlock as MessageContentReasoningBlock
);
concatenatedBlock = {};
}
concatenatedBlocks.push(block);
continue;
}

// non-redacted block
if ("reasoningText" in block && typeof block.reasoningText === "object") {
if ("redactedContent" in concatenatedBlock) {
// new type of block, so end the previous one
concatenatedBlocks.push(
concatenatedBlock as MessageContentReasoningBlock
);
concatenatedBlock = {};
}
const { text, signature } = block.reasoningText as Partial<
MessageContentReasoningBlockReasoningText["reasoningText"]
>;
const { text: prevText, signature: prevSignature } = (
"reasoningText" in concatenatedBlock
? concatenatedBlock.reasoningText
: {}
) as Partial<MessageContentReasoningBlockReasoningText["reasoningText"]>;

concatenatedBlock = {
type: "reasoning_content",
reasoningText: {
...((concatenatedBlock as MessageContentReasoningBlockReasoningText)
.reasoningText ?? {}),
...(prevText !== undefined || text !== undefined
? { text: (prevText ?? "") + (text ?? "") }
: {}),
...(prevSignature !== undefined || signature !== undefined
? { signature: (prevSignature ?? "") + (signature ?? "") }
: {}),
},
};
// if a partial block chunk has a signature, the next one will begin a new reasoning block.
// full blocks always have signatures, so we start one now, anyway
if ("signature" in block.reasoningText) {
concatenatedBlocks.push(
concatenatedBlock as MessageContentReasoningBlock
);
concatenatedBlock = {};
}
}

if ("redactedContent" in block) {
if ("reasoningText" in concatenatedBlock) {
// New type of block, so end the previous one. We should't really hit
// this, as we'll have created a new block upon encountering the
// signature above, but better safe than sorry.
concatenatedBlocks.push(
concatenatedBlock as MessageContentReasoningBlock
);
concatenatedBlock = {};
}
const { redactedContent } = block;
const prevRedactedContent = (
"redactedContent" in concatenatedBlock
? concatenatedBlock.redactedContent!
: ""
) as Partial<MessageContentReasoningBlockRedacted["redactedContent"]>;
concatenatedBlock = {
type: "reasoning_content",
redactedContent: prevRedactedContent + redactedContent,
};
}
}
if (Object.keys(concatenatedBlock).length > 0) {
concatenatedBlocks.push(concatenatedBlock as MessageContentReasoningBlock);
}
return concatenatedBlocks;
}
Loading