-
Notifications
You must be signed in to change notification settings - Fork 2.9k
feat(anthropic): implement Anthropic memory tool in provider package #9406
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
|
|
Nice! I guess what I'm not following here is how the files end up on the graph state. In your example, it looks like the files just end up in an object that's a global variable declared in the example file. Is there some magic I'm missing here to propagate this to / from the graph state? |
If your goal is it to store the file system in the graph as part of your agent you could do this via: import { z } from "zod";
import { ChatAnthropic, tools, StateFileSystem } from "@langchain/anthropic";
import { createAgent, createMiddleware } from "langchain";
const model = new ChatAnthropic({
model: "claude-sonnet-4-5-20250929",
temperature: 0,
clientOptions: {
defaultHeaders: {
"anthropic-beta": "context-management-2025-06-27",
},
},
});
const memoryMiddleware = () => {
const files = {};
const filesystem = new StateFileSystem(files, [], (files) => {
Object.assign(files, files);
});
return createMiddleware({
name: "MemoryMiddleware",
stateSchema: z.object({
files: z.record(
z.string(),
z.array(
z.object({
content: z.string(),
createdAt: z.string(),
})
)
),
}),
tools: [tools.memory({ filesystem })],
beforeAgent: async (state) => {
// update the filesystem with files within the state
await Promise.all(
Object.entries(state.files).map(async ([filepath, fileData]) =>
filesystem.writeFile(filepath, fileData)
)
);
},
});
};
const agent = createAgent({
model,
middleware: [memoryMiddleware],
});
const result = await agent.invoke({
messages: "What is the weather in Tokyo?",
});
console.log(result); |
|
Hmm still not sure I'm following. I see how the filesystem gets what's initially on the graph, but I'm still not seeing how it updates the graph state after the tool call |
|
It's part of the tool implementation in the provider package. By passing in a |
|
Maybe worth an integration test or two to show how that end-to-end pipeline works? Could just use / adapt mine |
@pokey has been attempting to implement the Anthropic memory tool as part of a middleware. This PR approaches this differently and implements this tool as provider tool. Running the tool within and agent shows that the model can interact with the tool:
{ messages: [ HumanMessage { "id": "b107d300-b413-41b4-9047-06b5e41fc063", "content": "What is the weather in Tokyo?", "additional_kwargs": {}, "response_metadata": {} }, AIMessage { "id": "msg_01CEiqjq2L7vATupz85S17CS", "content": [ { "type": "text", "text": "I'll check my memory first, then help you with the weather information." }, { "type": "tool_use", "id": "toolu_01W4GpFJk6wiEhPvrZy5efLP", "name": "memory", "input": { "command": "view", "path": "/memories" } } ], "name": "model", "additional_kwargs": { "model": "claude-sonnet-4-5-20250929", "id": "msg_01CEiqjq2L7vATupz85S17CS", "type": "message", "role": "assistant", "stop_reason": "tool_use", "stop_sequence": null, "usage": { "input_tokens": 1579, "cache_creation_input_tokens": 0, "cache_read_input_tokens": 0, "cache_creation": { "ephemeral_5m_input_tokens": 0, "ephemeral_1h_input_tokens": 0 }, "output_tokens": 85, "service_tier": "standard" }, "context_management": { "applied_edits": [] } }, "response_metadata": { "model": "claude-sonnet-4-5-20250929", "id": "msg_01CEiqjq2L7vATupz85S17CS", "stop_reason": "tool_use", "stop_sequence": null, "usage": { "input_tokens": 1579, "cache_creation_input_tokens": 0, "cache_read_input_tokens": 0, "cache_creation": { "ephemeral_5m_input_tokens": 0, "ephemeral_1h_input_tokens": 0 }, "output_tokens": 85, "service_tier": "standard" }, "context_management": { "applied_edits": [] }, "type": "message", "role": "assistant", "model_provider": "anthropic" }, "tool_calls": [ { "name": "memory", "args": { "command": "view", "path": "/memories" }, "id": "toolu_01W4GpFJk6wiEhPvrZy5efLP", "type": "tool_call" } ], "invalid_tool_calls": [], "usage_metadata": { "input_tokens": 1579, "output_tokens": 85, "total_tokens": 1664, "input_token_details": { "cache_creation": 0, "cache_read": 0 } } }, ToolMessage { "id": "c4ce0fb9-824c-4ca8-ae35-796e201d25c1", "content": "Directory: /memories\n- weather_in_tokyo.txt", "name": "memory", "additional_kwargs": {}, "response_metadata": {}, "tool_call_id": "toolu_01W4GpFJk6wiEhPvrZy5efLP" }, AIMessage { "id": "msg_015tZU7NaGgTRvKTUEUAq8Ls", "content": [ { "type": "text", "text": "Let me check what information I have about Tokyo's weather:" }, { "type": "tool_use", "id": "toolu_017gEW3kSusW1SotXxEdbEFX", "name": "memory", "input": { "command": "view", "path": "/memories/weather_in_tokyo.txt" } } ], "name": "model", "additional_kwargs": { "model": "claude-sonnet-4-5-20250929", "id": "msg_015tZU7NaGgTRvKTUEUAq8Ls", "type": "message", "role": "assistant", "stop_reason": "tool_use", "stop_sequence": null, "usage": { "input_tokens": 1689, "cache_creation_input_tokens": 0, "cache_read_input_tokens": 0, "cache_creation": { "ephemeral_5m_input_tokens": 0, "ephemeral_1h_input_tokens": 0 }, "output_tokens": 90, "service_tier": "standard" }, "context_management": { "applied_edits": [] } }, "response_metadata": { "model": "claude-sonnet-4-5-20250929", "id": "msg_015tZU7NaGgTRvKTUEUAq8Ls", "stop_reason": "tool_use", "stop_sequence": null, "usage": { "input_tokens": 1689, "cache_creation_input_tokens": 0, "cache_read_input_tokens": 0, "cache_creation": { "ephemeral_5m_input_tokens": 0, "ephemeral_1h_input_tokens": 0 }, "output_tokens": 90, "service_tier": "standard" }, "context_management": { "applied_edits": [] }, "type": "message", "role": "assistant", "model_provider": "anthropic" }, "tool_calls": [ { "name": "memory", "args": { "command": "view", "path": "/memories/weather_in_tokyo.txt" }, "id": "toolu_017gEW3kSusW1SotXxEdbEFX", "type": "tool_call" } ], "invalid_tool_calls": [], "usage_metadata": { "input_tokens": 1689, "output_tokens": 90, "total_tokens": 1779, "input_token_details": { "cache_creation": 0, "cache_read": 0 } } }, ToolMessage { "id": "a477ec43-f8e8-4e8a-b5d0-9b2ab25e0384", "content": "1|{\"weather\":\"sunny\",\"temperature\":20,\"humidity\":50,\"wind_speed\":10,\"wind_direction\":\"N\",\"wind_gust\":15,\"wind_gust_direction\":\"N\",\"wind_gust_speed\":20,\"time\":\"2025-11-14T07:41:48.543Z\"}", "name": "memory", "additional_kwargs": {}, "response_metadata": {}, "tool_call_id": "toolu_017gEW3kSusW1SotXxEdbEFX" }, AIMessage { "id": "msg_01UNpzE3YXH44wfWkNTkRtfH", "content": "I apologize, but I don't have access to real-time weather data or weather APIs. The information in my memory appears to be from a previous interaction and is not current.\n\nTo get accurate, up-to-date weather information for Tokyo, I recommend:\n\n1. **Weather websites**: Check weather.com, accuweather.com, or weather.gov\n2. **Search engines**: Simply search \"Tokyo weather\" on Google or Bing\n3. **Weather apps**: Use apps like Weather Channel, AccuWeather, or your phone's built-in weather app\n4. **Japan Meteorological Agency**: Visit their official website at jma.go.jp for official forecasts\n\nThese sources will provide you with current conditions, temperature, humidity, precipitation, and forecasts for Tokyo.", "name": "model", "additional_kwargs": { "model": "claude-sonnet-4-5-20250929", "id": "msg_01UNpzE3YXH44wfWkNTkRtfH", "type": "message", "role": "assistant", "stop_reason": "end_turn", "stop_sequence": null, "usage": { "input_tokens": 1861, "cache_creation_input_tokens": 0, "cache_read_input_tokens": 0, "cache_creation": { "ephemeral_5m_input_tokens": 0, "ephemeral_1h_input_tokens": 0 }, "output_tokens": 174, "service_tier": "standard" }, "context_management": { "applied_edits": [] } }, "response_metadata": { "model": "claude-sonnet-4-5-20250929", "id": "msg_01UNpzE3YXH44wfWkNTkRtfH", "stop_reason": "end_turn", "stop_sequence": null, "usage": { "input_tokens": 1861, "cache_creation_input_tokens": 0, "cache_read_input_tokens": 0, "cache_creation": { "ephemeral_5m_input_tokens": 0, "ephemeral_1h_input_tokens": 0 }, "output_tokens": 174, "service_tier": "standard" }, "context_management": { "applied_edits": [] }, "type": "message", "role": "assistant", "model_provider": "anthropic" }, "tool_calls": [], "invalid_tool_calls": [], "usage_metadata": { "input_tokens": 1861, "output_tokens": 174, "total_tokens": 2035, "input_token_details": { "cache_creation": 0, "cache_read": 0 } } } ] }I don't think there is any additional value in implementing this as middleware. What I like about this approach is that: