mirror of
https://github.com/langchain-ai/deepagentsjs.git
synced 2026-07-19 16:33:38 -04:00
feat(deepagents): add bedrockPromptCachingMiddleware to default stack (#611)
## Summary - Adds `bedrockPromptCachingMiddleware` to the default middleware stack - Adds unit tests for the Bedrock model detection util function covering the different formats of model strings/`ChatModel`s
This commit is contained in:
@@ -0,0 +1,7 @@
|
||||
---
|
||||
"deepagents": patch
|
||||
---
|
||||
|
||||
feat(deepagents): add bedrockPromptCachingMiddleware to default stack
|
||||
|
||||
Add bedrockPromptCachingMiddleware to default middleware stack. This automatically opts-in to Bedrock prompt caching for Nova and Anthropic models
|
||||
@@ -3,6 +3,7 @@ import {
|
||||
createMiddleware,
|
||||
humanInTheLoopMiddleware,
|
||||
anthropicPromptCachingMiddleware,
|
||||
bedrockPromptCachingMiddleware,
|
||||
todoListMiddleware,
|
||||
SystemMessage,
|
||||
type AgentMiddleware,
|
||||
@@ -61,6 +62,7 @@ import {
|
||||
isAnthropicModel,
|
||||
getModelProvider,
|
||||
getModelIdentifier,
|
||||
isBedrockConverseModel,
|
||||
} from "./utils.js";
|
||||
|
||||
const BASE_AGENT_PROMPT = context`
|
||||
@@ -220,15 +222,26 @@ export function createDeepAgent<
|
||||
: (tools as StructuredTool[]);
|
||||
|
||||
const anthropicModel = isAnthropicModel(model);
|
||||
const cacheMiddleware = anthropicModel
|
||||
? [
|
||||
anthropicPromptCachingMiddleware({
|
||||
unsupportedModelBehavior: "ignore",
|
||||
minMessagesToCache: 1,
|
||||
}),
|
||||
createCacheBreakpointMiddleware(),
|
||||
]
|
||||
: [];
|
||||
const bedrockModel = isBedrockConverseModel(model);
|
||||
let cacheMiddleware: AnyAgentMiddleware[] = [];
|
||||
|
||||
if (anthropicModel) {
|
||||
cacheMiddleware = [
|
||||
...cacheMiddleware,
|
||||
anthropicPromptCachingMiddleware({
|
||||
unsupportedModelBehavior: "ignore",
|
||||
minMessagesToCache: 1,
|
||||
}),
|
||||
createCacheBreakpointMiddleware(),
|
||||
];
|
||||
}
|
||||
|
||||
if (bedrockModel) {
|
||||
cacheMiddleware = [
|
||||
...cacheMiddleware,
|
||||
bedrockPromptCachingMiddleware({ unsupportedModelBehavior: "ignore" }),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Process subagents to add SkillsMiddleware for those with their own skills.
|
||||
|
||||
@@ -0,0 +1,86 @@
|
||||
import { describe, it, expect, vi } from "vitest";
|
||||
import { FakeListChatModel } from "@langchain/core/utils/testing";
|
||||
import { isBedrockConverseModel } from "./utils.js";
|
||||
|
||||
describe("isBedrockConverseModel", () => {
|
||||
describe("string inputs", () => {
|
||||
it("should detect bedrock: prefixed model strings", () => {
|
||||
expect(
|
||||
isBedrockConverseModel(
|
||||
"bedrock:anthropic.claude-3-5-sonnet-20240620-v1:0",
|
||||
),
|
||||
).toBe(true);
|
||||
|
||||
expect(
|
||||
isBedrockConverseModel(
|
||||
"bedrock:us.anthropic.claude-3-7-sonnet-20250219-v1:0",
|
||||
),
|
||||
).toBe(true);
|
||||
|
||||
expect(
|
||||
isBedrockConverseModel("bedrock:meta.llama3-70b-instruct-v1:0"),
|
||||
).toBe(true);
|
||||
});
|
||||
|
||||
it("should detect aws: prefixed model strings", () => {
|
||||
expect(
|
||||
isBedrockConverseModel("aws:anthropic.claude-3-5-sonnet-20240620-v1:0"),
|
||||
).toBe(true);
|
||||
expect(isBedrockConverseModel("aws:amazon.nova-pro-v1:0")).toBe(true);
|
||||
});
|
||||
|
||||
it("should detect bare amazon.* model strings (langchain auto-inference)", () => {
|
||||
expect(isBedrockConverseModel("amazon.nova-pro-v1:0")).toBe(true);
|
||||
expect(isBedrockConverseModel("amazon.titan-text-express-v1")).toBe(true);
|
||||
});
|
||||
|
||||
it("should reject non-Bedrock provider-prefixed model strings", () => {
|
||||
expect(isBedrockConverseModel("anthropic:claude-3-opus")).toBe(false);
|
||||
expect(isBedrockConverseModel("openai:gpt-4")).toBe(false);
|
||||
expect(isBedrockConverseModel("google:gemini-pro")).toBe(false);
|
||||
});
|
||||
|
||||
it("should reject bare non-amazon model strings", () => {
|
||||
expect(
|
||||
isBedrockConverseModel("anthropic.claude-3-5-sonnet-20240620-v1:0"),
|
||||
).toBe(false);
|
||||
expect(isBedrockConverseModel("meta.llama3-70b-instruct-v1:0")).toBe(
|
||||
false,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("model object inputs", () => {
|
||||
it("should detect ChatBedrockConverse model objects", () => {
|
||||
const model = new FakeListChatModel({ responses: [] });
|
||||
vi.spyOn(model, "getName").mockReturnValue("ChatBedrockConverse");
|
||||
expect(isBedrockConverseModel(model)).toBe(true);
|
||||
});
|
||||
|
||||
it("should reject non-Bedrock model objects", () => {
|
||||
const anthropic = new FakeListChatModel({ responses: [] });
|
||||
vi.spyOn(anthropic, "getName").mockReturnValue("ChatAnthropic");
|
||||
expect(isBedrockConverseModel(anthropic)).toBe(false);
|
||||
});
|
||||
|
||||
it("should detect ConfigurableModel wrapping the bedrock provider", () => {
|
||||
const model = new FakeListChatModel({ responses: [] });
|
||||
vi.spyOn(model, "getName").mockReturnValue("ConfigurableModel");
|
||||
(model as any)._defaultConfig = { modelProvider: "bedrock" };
|
||||
expect(isBedrockConverseModel(model)).toBe(true);
|
||||
});
|
||||
|
||||
it("should detect ConfigurableModel wrapping the aws provider alias", () => {
|
||||
const model = new FakeListChatModel({ responses: [] });
|
||||
vi.spyOn(model, "getName").mockReturnValue("ConfigurableModel");
|
||||
(model as any)._defaultConfig = { modelProvider: "aws" };
|
||||
expect(isBedrockConverseModel(model)).toBe(true);
|
||||
});
|
||||
|
||||
it("should reject ConfigurableModel with no _defaultConfig", () => {
|
||||
const model = new FakeListChatModel({ responses: [] });
|
||||
vi.spyOn(model, "getName").mockReturnValue("ConfigurableModel");
|
||||
expect(isBedrockConverseModel(model)).toBe(false);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -26,6 +26,36 @@ export function isAnthropicModel(
|
||||
return model.getName() === "ChatAnthropic";
|
||||
}
|
||||
|
||||
/**
|
||||
* Detect whether a model is an AWS Bedrock Converse model.
|
||||
*
|
||||
* Accepts the wider `RunnableInterface` shape (the type of `request.model`
|
||||
* inside `wrapModelCall`, aliased as `AgentLanguageModelLike` in langchain)
|
||||
* because the function only depends on `.getName()`, which is part of the
|
||||
* Runnable contract. `BaseLanguageModel` extends `Runnable`, so existing
|
||||
* call sites still type-check.
|
||||
*/
|
||||
export function isBedrockConverseModel(
|
||||
model: BaseLanguageModel | RunnableInterface<unknown, unknown> | string,
|
||||
): boolean {
|
||||
if (typeof model === "string") {
|
||||
// Explicit provider prefix (`bedrock:` or `aws:`) — both map to
|
||||
// ChatBedrockConverse in langchain's initChatModel.
|
||||
const colonIdx = model.indexOf(":");
|
||||
if (colonIdx !== -1) {
|
||||
const prefix = model.slice(0, colonIdx);
|
||||
if (prefix === "bedrock" || prefix === "aws") return true;
|
||||
}
|
||||
|
||||
return model.startsWith("amazon.");
|
||||
}
|
||||
if (model.getName() === "ConfigurableModel") {
|
||||
const provider = (model as any)._defaultConfig?.modelProvider;
|
||||
return provider === "bedrock" || provider === "aws";
|
||||
}
|
||||
return model.getName() === "ChatBedrockConverse";
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract the provider name from a model instance for profile lookup.
|
||||
*
|
||||
|
||||
Reference in New Issue
Block a user