Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions libs/langchain/src/agents/nodes/AgentNode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -806,6 +806,7 @@ export class AgentNode<
*/
const modelWithTools = await bindTools(model, allTools, {
...options,
...(preparedOptions?.modelSettings ?? {}),
tool_choice: toolChoice,
});

Expand Down
16 changes: 16 additions & 0 deletions libs/langchain/src/agents/nodes/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,4 +54,20 @@ export interface ModelRequest<
* The runtime context containing metadata, signal, writer, interrupt, etc.
*/
runtime: Runtime<TContext>;

/**
* Additional settings to bind to the model when preparing it for invocation.
* These settings are applied via `bindTools()` and can include parameters like
* `headers`, `container`, etc. The model is re-bound on each request,
* so these settings can vary per invocation.
*
* @example
* ```ts
* modelSettings: {
* headers: { "anthropic-beta": "code-execution-2025-08-25" },
* container: "container_abc123"
* }
* ```
*/
modelSettings?: Record<string, unknown>;
}
74 changes: 74 additions & 0 deletions libs/langchain/src/agents/tests/modelSettings.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import { ChatAnthropic } from "@langchain/anthropic";
import { HumanMessage } from "@langchain/core/messages";
import { describe, expect, it, vi } from "vitest";
import { createAgent, createMiddleware } from "../index.js";

describe("modelSettings middleware support", () => {
it("should pass modelSettings to real Anthropic model via bindTools", async () => {
// Mock the Anthropic client
const mockCreate = vi.fn().mockResolvedValue({
id: "msg_123",
type: "message",
role: "assistant",
content: [{ type: "text", text: "Response from model" }],
model: "claude-sonnet-4-20250514",
stop_reason: "end_turn",
usage: { input_tokens: 10, output_tokens: 20 },
});

const mockClient = {
messages: {
create: mockCreate,
},
};

// Create real ChatAnthropic with mocked client
const model = new ChatAnthropic({
model: "claude-sonnet-4-20250514",
temperature: 0,
// eslint-disable-next-line @typescript-eslint/no-explicit-any
createClient: () => mockClient as any,
});

const middleware = createMiddleware({
name: "testMiddleware",
wrapModelCall: async (request, handler) => {
return handler({
...request,
modelSettings: {
headers: {
"anthropic-beta":
"code-execution-2025-08-25,files-api-2025-04-14",
},
container: "container_abc123",
},
});
},
});

const agent = createAgent({
model,
tools: [],
middleware: [middleware] as const,
});

await agent.invoke({
messages: [new HumanMessage("Hello, world!")],
});

// Verify the client was called
expect(mockCreate).toHaveBeenCalled();

// Check the actual parameters passed to the Anthropic client
const clientCallArgs = mockCreate.mock.calls[0][0];
expect(clientCallArgs).toHaveProperty("container", "container_abc123");

// Check that headers were passed via options (second parameter)
const clientOptions = mockCreate.mock.calls[0][1];
expect(clientOptions).toHaveProperty("headers");
expect(clientOptions.headers).toHaveProperty(
"anthropic-beta",
"code-execution-2025-08-25,files-api-2025-04-14"
);
});
});