diff --git a/docs/docs/integrations/chat/anthropic.ipynb b/docs/docs/integrations/chat/anthropic.ipynb
index 4ac890e18b9..d9f2ea00d22 100644
--- a/docs/docs/integrations/chat/anthropic.ipynb
+++ b/docs/docs/integrations/chat/anthropic.ipynb
@@ -102,6 +102,16 @@
"%pip install -qU langchain-anthropic"
]
},
+ {
+ "cell_type": "markdown",
+ "id": "fe4993ad-4a9b-4021-8ebd-f0fbbc739f49",
+ "metadata": {},
+ "source": [
+ ":::info This guide requires ``langchain-anthropic>=0.3.10``\n",
+ "\n",
+ ":::"
+ ]
+ },
{
"cell_type": "markdown",
"id": "a38cde65-254d-4219-a441-068766c0d4b5",
@@ -245,7 +255,7 @@
"source": [
"## Content blocks\n",
"\n",
- "One key difference to note between Anthropic models and most others is that the contents of a single Anthropic AI message can either be a single string or a **list of content blocks**. For example when an Anthropic model invokes a tool, the tool invocation is part of the message content (as well as being exposed in the standardized `AIMessage.tool_calls`):"
+ "Content from a single Anthropic AI message can either be a single string or a **list of content blocks**. For example when an Anthropic model invokes a tool, the tool invocation is part of the message content (as well as being exposed in the standardized `AIMessage.tool_calls`):"
]
},
{
@@ -368,6 +378,377 @@
"print(json.dumps(response.content, indent=2))"
]
},
+ {
+ "cell_type": "markdown",
+ "id": "34349dfe-5d81-4887-a4f4-cd01e9587cdc",
+ "metadata": {},
+ "source": [
+ "## Prompt caching\n",
+ "\n",
+ "Anthropic supports [caching](https://docs.anthropic.com/en/docs/build-with-claude/prompt-caching) of [elements of your prompts](https://docs.anthropic.com/en/docs/build-with-claude/prompt-caching#what-can-be-cached), including messages, tool definitions, tool results, images and documents. This allows you to re-use large documents, instructions, [few-shot documents](/docs/concepts/few_shot_prompting/), and other data to reduce latency and costs.\n",
+ "\n",
+ "To enable caching on an element of a prompt, mark its associated content block using the `cache_control` key. See examples below:\n",
+ "\n",
+ "### Messages"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 1,
+ "id": "babb44a5-33f7-4200-9dfc-be867cf2c217",
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "First invocation:\n",
+ "{'cache_read': 0, 'cache_creation': 1458}\n",
+ "\n",
+ "Second:\n",
+ "{'cache_read': 1458, 'cache_creation': 0}\n"
+ ]
+ }
+ ],
+ "source": [
+ "import requests\n",
+ "from langchain_anthropic import ChatAnthropic\n",
+ "\n",
+ "llm = ChatAnthropic(model=\"claude-3-7-sonnet-20250219\")\n",
+ "\n",
+ "# Pull LangChain readme\n",
+ "get_response = requests.get(\n",
+ " \"https://raw.githubusercontent.com/langchain-ai/langchain/master/README.md\"\n",
+ ")\n",
+ "readme = get_response.text\n",
+ "\n",
+ "messages = [\n",
+ " {\n",
+ " \"role\": \"system\",\n",
+ " \"content\": [\n",
+ " {\n",
+ " \"type\": \"text\",\n",
+ " \"text\": \"You are a technology expert.\",\n",
+ " },\n",
+ " {\n",
+ " \"type\": \"text\",\n",
+ " \"text\": f\"{readme}\",\n",
+ " # highlight-next-line\n",
+ " \"cache_control\": {\"type\": \"ephemeral\"},\n",
+ " },\n",
+ " ],\n",
+ " },\n",
+ " {\n",
+ " \"role\": \"user\",\n",
+ " \"content\": \"What's LangChain, according to its README?\",\n",
+ " },\n",
+ "]\n",
+ "\n",
+ "response_1 = llm.invoke(messages)\n",
+ "response_2 = llm.invoke(messages)\n",
+ "\n",
+ "usage_1 = response_1.usage_metadata[\"input_token_details\"]\n",
+ "usage_2 = response_2.usage_metadata[\"input_token_details\"]\n",
+ "\n",
+ "print(f\"First invocation:\\n{usage_1}\")\n",
+ "print(f\"\\nSecond:\\n{usage_2}\")"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "141ce9c5-012d-4502-9d61-4a413b5d959a",
+ "metadata": {},
+ "source": [
+ "### Tools"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 2,
+ "id": "1de82015-810f-4ed4-a08b-9866ea8746ce",
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "First invocation:\n",
+ "{'cache_read': 0, 'cache_creation': 1809}\n",
+ "\n",
+ "Second:\n",
+ "{'cache_read': 1809, 'cache_creation': 0}\n"
+ ]
+ }
+ ],
+ "source": [
+ "from langchain_anthropic import convert_to_anthropic_tool\n",
+ "from langchain_core.tools import tool\n",
+ "\n",
+ "# For demonstration purposes, we artificially expand the\n",
+ "# tool description.\n",
+ "description = (\n",
+ " f\"Get the weather at a location. By the way, check out this readme: {readme}\"\n",
+ ")\n",
+ "\n",
+ "\n",
+ "@tool(description=description)\n",
+ "def get_weather(location: str) -> str:\n",
+ " return \"It's sunny.\"\n",
+ "\n",
+ "\n",
+ "# Enable caching on the tool\n",
+ "# highlight-start\n",
+ "weather_tool = convert_to_anthropic_tool(get_weather)\n",
+ "weather_tool[\"cache_control\"] = {\"type\": \"ephemeral\"}\n",
+ "# highlight-end\n",
+ "\n",
+ "llm = ChatAnthropic(model=\"claude-3-7-sonnet-20250219\")\n",
+ "llm_with_tools = llm.bind_tools([weather_tool])\n",
+ "query = \"What's the weather in San Francisco?\"\n",
+ "\n",
+ "response_1 = llm_with_tools.invoke(query)\n",
+ "response_2 = llm_with_tools.invoke(query)\n",
+ "\n",
+ "usage_1 = response_1.usage_metadata[\"input_token_details\"]\n",
+ "usage_2 = response_2.usage_metadata[\"input_token_details\"]\n",
+ "\n",
+ "print(f\"First invocation:\\n{usage_1}\")\n",
+ "print(f\"\\nSecond:\\n{usage_2}\")"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "a763830d-82cb-448a-ab30-f561522791b9",
+ "metadata": {},
+ "source": [
+ "### Incremental caching in conversational applications\n",
+ "\n",
+ "Prompt caching can be used in [multi-turn conversations](https://docs.anthropic.com/en/docs/build-with-claude/prompt-caching#continuing-a-multi-turn-conversation) to maintain context from earlier messages without redundant processing.\n",
+ "\n",
+ "We can enable incremental caching by marking the final message with `cache_control`. Claude will automatically use the longest previously-cacched prefix for follow-up messages.\n",
+ "\n",
+ "Below, we implement a simple chatbot that incorporates this feature. We follow the LangChain [chatbot tutorial](/docs/tutorials/chatbot/), but add a custom [reducer](https://langchain-ai.github.io/langgraph/concepts/low_level/#reducers) that automatically marks the last content block in each user message with `cache_control`. See below:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 2,
+ "id": "07fde4db-344c-49bc-a5b4-99e2d20fb394",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "import requests\n",
+ "from langchain_anthropic import ChatAnthropic\n",
+ "from langgraph.checkpoint.memory import MemorySaver\n",
+ "from langgraph.graph import START, StateGraph, add_messages\n",
+ "from typing_extensions import Annotated, TypedDict\n",
+ "\n",
+ "llm = ChatAnthropic(model=\"claude-3-7-sonnet-20250219\")\n",
+ "\n",
+ "# Pull LangChain readme\n",
+ "get_response = requests.get(\n",
+ " \"https://raw.githubusercontent.com/langchain-ai/langchain/master/README.md\"\n",
+ ")\n",
+ "readme = get_response.text\n",
+ "\n",
+ "\n",
+ "def messages_reducer(left: list, right: list) -> list:\n",
+ " # Update last user message\n",
+ " for i in range(len(right) - 1, -1, -1):\n",
+ " if right[i].type == \"human\":\n",
+ " right[i].content[-1][\"cache_control\"] = {\"type\": \"ephemeral\"}\n",
+ " break\n",
+ "\n",
+ " return add_messages(left, right)\n",
+ "\n",
+ "\n",
+ "class State(TypedDict):\n",
+ " messages: Annotated[list, messages_reducer]\n",
+ "\n",
+ "\n",
+ "workflow = StateGraph(state_schema=State)\n",
+ "\n",
+ "\n",
+ "# Define the function that calls the model\n",
+ "def call_model(state: State):\n",
+ " response = llm.invoke(state[\"messages\"])\n",
+ " return {\"messages\": [response]}\n",
+ "\n",
+ "\n",
+ "# Define the (single) node in the graph\n",
+ "workflow.add_edge(START, \"model\")\n",
+ "workflow.add_node(\"model\", call_model)\n",
+ "\n",
+ "# Add memory\n",
+ "memory = MemorySaver()\n",
+ "app = workflow.compile(checkpointer=memory)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 3,
+ "id": "40013035-eb22-4327-8aaf-1ee974d9ff46",
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "==================================\u001b[1m Ai Message \u001b[0m==================================\n",
+ "\n",
+ "Hello, Bob! It's nice to meet you. How are you doing today? Is there something I can help you with?\n",
+ "\n",
+ "{'cache_read': 0, 'cache_creation': 0}\n"
+ ]
+ }
+ ],
+ "source": [
+ "from langchain_core.messages import HumanMessage\n",
+ "\n",
+ "config = {\"configurable\": {\"thread_id\": \"abc123\"}}\n",
+ "\n",
+ "query = \"Hi! I'm Bob.\"\n",
+ "\n",
+ "input_message = HumanMessage([{\"type\": \"text\", \"text\": query}])\n",
+ "output = app.invoke({\"messages\": [input_message]}, config)\n",
+ "output[\"messages\"][-1].pretty_print()\n",
+ "print(f'\\n{output[\"messages\"][-1].usage_metadata[\"input_token_details\"]}')"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 4,
+ "id": "22371f68-7913-4c4f-ab4a-2b4265095469",
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "==================================\u001b[1m Ai Message \u001b[0m==================================\n",
+ "\n",
+ "I can see you've shared the README from the LangChain GitHub repository. This is the documentation for LangChain, which is a popular framework for building applications powered by Large Language Models (LLMs). Here's a summary of what the README contains:\n",
+ "\n",
+ "LangChain is:\n",
+ "- A framework for developing LLM-powered applications\n",
+ "- Helps chain together components and integrations to simplify AI application development\n",
+ "- Provides a standard interface for models, embeddings, vector stores, etc.\n",
+ "\n",
+ "Key features/benefits:\n",
+ "- Real-time data augmentation (connect LLMs to diverse data sources)\n",
+ "- Model interoperability (swap models easily as needed)\n",
+ "- Large ecosystem of integrations\n",
+ "\n",
+ "The LangChain ecosystem includes:\n",
+ "- LangSmith - For evaluations and observability\n",
+ "- LangGraph - For building complex agents with customizable architecture\n",
+ "- LangGraph Platform - For deployment and scaling of agents\n",
+ "\n",
+ "The README also mentions installation instructions (`pip install -U langchain`) and links to various resources including tutorials, how-to guides, conceptual guides, and API references.\n",
+ "\n",
+ "Is there anything specific about LangChain you'd like to know more about, Bob?\n",
+ "\n",
+ "{'cache_read': 0, 'cache_creation': 1498}\n"
+ ]
+ }
+ ],
+ "source": [
+ "query = f\"Check out this readme: {readme}\"\n",
+ "\n",
+ "input_message = HumanMessage([{\"type\": \"text\", \"text\": query}])\n",
+ "output = app.invoke({\"messages\": [input_message]}, config)\n",
+ "output[\"messages\"][-1].pretty_print()\n",
+ "print(f'\\n{output[\"messages\"][-1].usage_metadata[\"input_token_details\"]}')"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 5,
+ "id": "0e6798fc-8a80-4324-b4e3-f18706256c61",
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "==================================\u001b[1m Ai Message \u001b[0m==================================\n",
+ "\n",
+ "Your name is Bob. You introduced yourself at the beginning of our conversation.\n",
+ "\n",
+ "{'cache_read': 1498, 'cache_creation': 269}\n"
+ ]
+ }
+ ],
+ "source": [
+ "query = \"What was my name again?\"\n",
+ "\n",
+ "input_message = HumanMessage([{\"type\": \"text\", \"text\": query}])\n",
+ "output = app.invoke({\"messages\": [input_message]}, config)\n",
+ "output[\"messages\"][-1].pretty_print()\n",
+ "print(f'\\n{output[\"messages\"][-1].usage_metadata[\"input_token_details\"]}')"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "aa4b3647-c672-4782-a88c-a55fd3bf969f",
+ "metadata": {},
+ "source": [
+ "In the [LangSmith trace](https://smith.langchain.com/public/4d0584d8-5f9e-4b91-8704-93ba2ccf416a/r), toggling \"raw output\" will show exactly what messages are sent to the chat model, including `cache_control` keys."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "029009f2-2795-418b-b5fc-fb996c6fe99e",
+ "metadata": {},
+ "source": [
+ "## Token-efficient tool use\n",
+ "\n",
+ "Anthropic supports a (beta) [token-efficient tool use](https://docs.anthropic.com/en/docs/build-with-claude/tool-use/token-efficient-tool-use) feature. To use it, specify the relevant beta-headers when instantiating the model."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 1,
+ "id": "206cff65-33b8-4a88-9b1a-050b4d57772a",
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "[{'name': 'get_weather', 'args': {'location': 'San Francisco'}, 'id': 'toolu_01EoeE1qYaePcmNbUvMsWtmA', 'type': 'tool_call'}]\n",
+ "\n",
+ "Total tokens: 408\n"
+ ]
+ }
+ ],
+ "source": [
+ "from langchain_anthropic import ChatAnthropic\n",
+ "from langchain_core.tools import tool\n",
+ "\n",
+ "llm = ChatAnthropic(\n",
+ " model=\"claude-3-7-sonnet-20250219\",\n",
+ " temperature=0,\n",
+ " # highlight-start\n",
+ " model_kwargs={\n",
+ " \"extra_headers\": {\"anthropic-beta\": \"token-efficient-tools-2025-02-19\"}\n",
+ " },\n",
+ " # highlight-end\n",
+ ")\n",
+ "\n",
+ "\n",
+ "@tool\n",
+ "def get_weather(location: str) -> str:\n",
+ " \"\"\"Get the weather at a location.\"\"\"\n",
+ " return \"It's sunny.\"\n",
+ "\n",
+ "\n",
+ "llm_with_tools = llm.bind_tools([get_weather])\n",
+ "response = llm_with_tools.invoke(\"What's the weather in San Francisco?\")\n",
+ "print(response.tool_calls)\n",
+ "print(f'\\nTotal tokens: {response.usage_metadata[\"total_tokens\"]}')"
+ ]
+ },
{
"cell_type": "markdown",
"id": "301d372f-4dec-43e6-b58c-eee25633e1a6",
@@ -525,6 +906,58 @@
"response.content"
]
},
+ {
+ "cell_type": "markdown",
+ "id": "cbfec7a9-d9df-4d12-844e-d922456dd9bf",
+ "metadata": {},
+ "source": [
+ "## Built-in tools\n",
+ "\n",
+ "Anthropic supports a variety of [built-in tools](https://docs.anthropic.com/en/docs/build-with-claude/tool-use/text-editor-tool), which can be bound to the model in the [usual way](/docs/how_to/tool_calling/). Claude will generate tool calls adhering to its internal schema for the tool:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 1,
+ "id": "30a0af36-2327-4b1d-9ba5-e47cb72db0be",
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "I'd be happy to help you fix the syntax error in your primes.py file. First, let's look at the current content of the file to identify the error.\n"
+ ]
+ },
+ {
+ "data": {
+ "text/plain": [
+ "[{'name': 'str_replace_editor',\n",
+ " 'args': {'command': 'view', 'path': '/repo/primes.py'},\n",
+ " 'id': 'toolu_01VdNgt1YV7kGfj9LFLm6HyQ',\n",
+ " 'type': 'tool_call'}]"
+ ]
+ },
+ "execution_count": 1,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "from langchain_anthropic import ChatAnthropic\n",
+ "\n",
+ "llm = ChatAnthropic(model=\"claude-3-7-sonnet-20250219\")\n",
+ "\n",
+ "tool = {\"type\": \"text_editor_20250124\", \"name\": \"str_replace_editor\"}\n",
+ "llm_with_tools = llm.bind_tools([tool])\n",
+ "\n",
+ "response = llm_with_tools.invoke(\n",
+ " \"There's a syntax error in my primes.py file. Can you help me fix it?\"\n",
+ ")\n",
+ "print(response.text())\n",
+ "response.tool_calls"
+ ]
+ },
{
"cell_type": "markdown",
"id": "3a5bb5ca-c3ae-4a58-be67-2cd18574b9a3",
diff --git a/libs/partners/anthropic/langchain_anthropic/__init__.py b/libs/partners/anthropic/langchain_anthropic/__init__.py
index 1ad0801abf1..4dcf4eb1878 100644
--- a/libs/partners/anthropic/langchain_anthropic/__init__.py
+++ b/libs/partners/anthropic/langchain_anthropic/__init__.py
@@ -1,4 +1,14 @@
-from langchain_anthropic.chat_models import ChatAnthropic, ChatAnthropicMessages
+from langchain_anthropic.chat_models import (
+ ChatAnthropic,
+ ChatAnthropicMessages,
+ convert_to_anthropic_tool,
+)
from langchain_anthropic.llms import Anthropic, AnthropicLLM
-__all__ = ["ChatAnthropicMessages", "ChatAnthropic", "Anthropic", "AnthropicLLM"]
+__all__ = [
+ "ChatAnthropicMessages",
+ "ChatAnthropic",
+ "convert_to_anthropic_tool",
+ "Anthropic",
+ "AnthropicLLM",
+]
diff --git a/libs/partners/anthropic/langchain_anthropic/chat_models.py b/libs/partners/anthropic/langchain_anthropic/chat_models.py
index eddcac746b1..02c21741a8c 100644
--- a/libs/partners/anthropic/langchain_anthropic/chat_models.py
+++ b/libs/partners/anthropic/langchain_anthropic/chat_models.py
@@ -93,6 +93,22 @@ class AnthropicTool(TypedDict):
cache_control: NotRequired[Dict[str, str]]
+def _is_builtin_tool(tool: Any) -> bool:
+ if not isinstance(tool, dict):
+ return False
+
+ tool_type = tool.get("type")
+ if not tool_type or not isinstance(tool_type, str):
+ return False
+
+ _builtin_tool_prefixes = [
+ "text_editor_",
+ "computer_",
+ "bash_",
+ ]
+ return any(tool_type.startswith(prefix) for prefix in _builtin_tool_prefixes)
+
+
def _format_image(image_url: str) -> Dict:
"""
Formats an image of format data:image/jpeg;base64,{b64_string}
@@ -669,6 +685,109 @@ class ChatAnthropic(BaseChatModel):
These can be disabled by setting ``stream_usage=False`` in the stream method,
or by setting ``stream_usage=False`` when initializing ChatAnthropic.
+ Prompt caching:
+ See LangChain `docs `_
+ for more detail.
+
+ .. code-block:: python
+
+ from langchain_anthropic import ChatAnthropic
+
+ llm = ChatAnthropic(model="claude-3-7-sonnet-20250219")
+
+ messages = [
+ {
+ "role": "system",
+ "content": [
+ {
+ "type": "text",
+ "text": "Below is some long context:",
+ },
+ {
+ "type": "text",
+ "text": f"{long_text}",
+ "cache_control": {"type": "ephemeral"},
+ },
+ ],
+ },
+ {
+ "role": "user",
+ "content": "What's that about?",
+ },
+ ]
+
+ response = llm.invoke(messages)
+ response.usage_metadata["input_token_details"]
+
+ .. code-block:: python
+
+ {'cache_read': 0, 'cache_creation': 1458}
+
+ Token-efficient tool use (beta):
+ See LangChain `docs `_
+ for more detail.
+
+ .. code-block:: python
+
+ from langchain_anthropic import ChatAnthropic
+ from langchain_core.tools import tool
+
+ llm = ChatAnthropic(
+ model="claude-3-7-sonnet-20250219",
+ temperature=0,
+ model_kwargs={
+ "extra_headers": {
+ "anthropic-beta": "token-efficient-tools-2025-02-19"
+ }
+ }
+ )
+
+ @tool
+ def get_weather(location: str) -> str:
+ \"\"\"Get the weather at a location.\"\"\"
+ return "It's sunny."
+
+ llm_with_tools = llm.bind_tools([get_weather])
+ response = llm_with_tools.invoke(
+ "What's the weather in San Francisco?"
+ )
+ print(response.tool_calls)
+ print(f'Total tokens: {response.usage_metadata["total_tokens"]}')
+
+ .. code-block:: none
+
+ [{'name': 'get_weather', 'args': {'location': 'San Francisco'}, 'id': 'toolu_01HLjQMSb1nWmgevQUtEyz17', 'type': 'tool_call'}]
+
+ Total tokens: 408
+
+ Built-in tools:
+ See LangChain `docs `_
+ for more detail.
+
+ .. code-block:: python
+
+ from langchain_anthropic import ChatAnthropic
+
+ llm = ChatAnthropic(model="claude-3-7-sonnet-20250219")
+
+ tool = {"type": "text_editor_20250124", "name": "str_replace_editor"}
+ llm_with_tools = llm.bind_tools([tool])
+
+ response = llm_with_tools.invoke(
+ "There's a syntax error in my primes.py file. Can you help me fix it?"
+ )
+ print(response.text())
+ response.tool_calls
+
+ .. code-block:: none
+
+ I'd be happy to help you fix the syntax error in your primes.py file. First, let's look at the current content of the file to identify the error.
+
+ [{'name': 'str_replace_editor',
+ 'args': {'command': 'view', 'path': '/repo/primes.py'},
+ 'id': 'toolu_01VdNgt1YV7kGfj9LFLm6HyQ',
+ 'type': 'tool_call'}]
+
Response metadata
.. code-block:: python
@@ -1156,7 +1275,6 @@ class ChatAnthropic(BaseChatModel):
llm = ChatAnthropic(
model="claude-3-5-sonnet-20240620",
temperature=0,
- extra_headers={"anthropic-beta": "prompt-caching-2024-07-31"}
)
llm_with_tools = llm.bind_tools([GetWeather, cached_price_tool])
llm_with_tools.invoke("what is the weather like in San Francisco",)
@@ -1174,7 +1292,10 @@ class ChatAnthropic(BaseChatModel):
AIMessage(content=[{'text': 'To get the current weather in San Francisco, I can use the GetWeather function. Let me check that for you.', 'type': 'text'}, {'id': 'toolu_01HtVtY1qhMFdPprx42qU2eA', 'input': {'location': 'San Francisco, CA'}, 'name': 'GetWeather', 'type': 'tool_use'}], response_metadata={'id': 'msg_016RfWHrRvW6DAGCdwB6Ac64', 'model': 'claude-3-5-sonnet-20240620', 'stop_reason': 'tool_use', 'stop_sequence': None, 'usage': {'input_tokens': 171, 'output_tokens': 82, 'cache_creation_input_tokens': 0, 'cache_read_input_tokens': 1470}}, id='run-88b1f825-dcb7-4277-ac27-53df55d22001-0', tool_calls=[{'name': 'GetWeather', 'args': {'location': 'San Francisco, CA'}, 'id': 'toolu_01HtVtY1qhMFdPprx42qU2eA', 'type': 'tool_call'}], usage_metadata={'input_tokens': 171, 'output_tokens': 82, 'total_tokens': 253})
""" # noqa: E501
- formatted_tools = [convert_to_anthropic_tool(tool) for tool in tools]
+ formatted_tools = [
+ tool if _is_builtin_tool(tool) else convert_to_anthropic_tool(tool)
+ for tool in tools
+ ]
if not tool_choice:
pass
elif isinstance(tool_choice, dict):
diff --git a/libs/partners/anthropic/pyproject.toml b/libs/partners/anthropic/pyproject.toml
index b4bd0ea4a61..8577ad38f2e 100644
--- a/libs/partners/anthropic/pyproject.toml
+++ b/libs/partners/anthropic/pyproject.toml
@@ -7,8 +7,8 @@ authors = []
license = { text = "MIT" }
requires-python = "<4.0,>=3.9"
dependencies = [
- "anthropic<1,>=0.47.0",
- "langchain-core<1.0.0,>=0.3.41",
+ "anthropic<1,>=0.49.0",
+ "langchain-core<1.0.0,>=0.3.44",
"pydantic<3.0.0,>=2.7.4",
]
name = "langchain-anthropic"
diff --git a/libs/partners/anthropic/tests/integration_tests/test_chat_models.py b/libs/partners/anthropic/tests/integration_tests/test_chat_models.py
index c61817972f3..e792c1583fd 100644
--- a/libs/partners/anthropic/tests/integration_tests/test_chat_models.py
+++ b/libs/partners/anthropic/tests/integration_tests/test_chat_models.py
@@ -380,20 +380,21 @@ async def test_astreaming() -> None:
def test_tool_use() -> None:
- llm = ChatAnthropic(model=MODEL_NAME)
- llm_with_tools = llm.bind_tools(
- [
- {
- "name": "get_weather",
- "description": "Get weather report for a city",
- "input_schema": {
- "type": "object",
- "properties": {"location": {"type": "string"}},
- },
- }
- ]
+ llm = ChatAnthropic(
+ model="claude-3-7-sonnet-20250219",
+ temperature=0,
)
- response = llm_with_tools.invoke("what's the weather in san francisco, ca")
+ tool_definition = {
+ "name": "get_weather",
+ "description": "Get weather report for a city",
+ "input_schema": {
+ "type": "object",
+ "properties": {"location": {"type": "string"}},
+ },
+ }
+ llm_with_tools = llm.bind_tools([tool_definition])
+ query = "how are you? what's the weather in san francisco, ca"
+ response = llm_with_tools.invoke(query)
assert isinstance(response, AIMessage)
assert isinstance(response.content, list)
assert isinstance(response.tool_calls, list)
@@ -404,10 +405,18 @@ def test_tool_use() -> None:
assert "location" in tool_call["args"]
# Test streaming
- input = "how are you? what's the weather in san francisco, ca"
+ llm = ChatAnthropic(
+ model="claude-3-7-sonnet-20250219",
+ temperature=0,
+ # Add extra headers to also test token-efficient tools
+ model_kwargs={
+ "extra_headers": {"anthropic-beta": "token-efficient-tools-2025-02-19"}
+ },
+ )
+ llm_with_tools = llm.bind_tools([tool_definition])
first = True
chunks = [] # type: ignore
- for chunk in llm_with_tools.stream(input):
+ for chunk in llm_with_tools.stream(query):
chunks = chunks + [chunk]
if first:
gathered = chunk
@@ -435,10 +444,19 @@ def test_tool_use() -> None:
assert "location" in tool_call["args"]
assert tool_call["id"] is not None
+ # Testing token-efficient tools
+ # https://docs.anthropic.com/en/docs/build-with-claude/tool-use/token-efficient-tool-use
+ assert gathered.usage_metadata
+ assert response.usage_metadata
+ assert (
+ gathered.usage_metadata["total_tokens"]
+ < response.usage_metadata["total_tokens"]
+ )
+
# Test passing response back to model
stream = llm_with_tools.stream(
[
- input,
+ query,
gathered,
ToolMessage(content="sunny and warm", tool_call_id=tool_call["id"]),
]
@@ -455,6 +473,17 @@ def test_tool_use() -> None:
assert len(chunks) > 1
+def test_builtin_tools() -> None:
+ llm = ChatAnthropic(model="claude-3-7-sonnet-20250219")
+ tool = {"type": "text_editor_20250124", "name": "str_replace_editor"}
+ llm_with_tools = llm.bind_tools([tool])
+ response = llm_with_tools.invoke(
+ "There's a syntax error in my primes.py file. Can you help me fix it?"
+ )
+ assert isinstance(response, AIMessage)
+ assert response.tool_calls
+
+
class GenerateUsername(BaseModel):
"Get a username based on someone's name and hair color."
diff --git a/libs/partners/anthropic/tests/unit_tests/test_imports.py b/libs/partners/anthropic/tests/unit_tests/test_imports.py
index e714099037a..b156a463174 100644
--- a/libs/partners/anthropic/tests/unit_tests/test_imports.py
+++ b/libs/partners/anthropic/tests/unit_tests/test_imports.py
@@ -1,6 +1,12 @@
from langchain_anthropic import __all__
-EXPECTED_ALL = ["ChatAnthropicMessages", "ChatAnthropic", "Anthropic", "AnthropicLLM"]
+EXPECTED_ALL = [
+ "ChatAnthropicMessages",
+ "ChatAnthropic",
+ "convert_to_anthropic_tool",
+ "Anthropic",
+ "AnthropicLLM",
+]
def test_all_imports() -> None:
diff --git a/libs/partners/anthropic/uv.lock b/libs/partners/anthropic/uv.lock
index 272e86576b3..aaa8fe1076a 100644
--- a/libs/partners/anthropic/uv.lock
+++ b/libs/partners/anthropic/uv.lock
@@ -17,7 +17,7 @@ wheels = [
[[package]]
name = "anthropic"
-version = "0.47.0"
+version = "0.49.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "anyio" },
@@ -28,9 +28,9 @@ dependencies = [
{ name = "sniffio" },
{ name = "typing-extensions" },
]
-sdist = { url = "https://files.pythonhosted.org/packages/7c/bf/39f8fd5f199bcdc5f5af8050f888c5928100c3c138b8292fcb8ba6535d80/anthropic-0.47.0.tar.gz", hash = "sha256:6e19994d3a9fc7527c8505b62b1494ca3f39d6bb993a4885014575c09905ebfc", size = 207642 }
+sdist = { url = "https://files.pythonhosted.org/packages/86/e3/a88c8494ce4d1a88252b9e053607e885f9b14d0a32273d47b727cbee4228/anthropic-0.49.0.tar.gz", hash = "sha256:c09e885b0f674b9119b4f296d8508907f6cff0009bc20d5cf6b35936c40b4398", size = 210016 }
wheels = [
- { url = "https://files.pythonhosted.org/packages/4a/62/c43e334ae8c1ea90f5d763016df8e4ef058643a2dbcf78d8cb63cbc474bc/anthropic-0.47.0-py3-none-any.whl", hash = "sha256:294b10e9ca800f57949f635be7b611e8ecfe8f9f2a56a2c165200841a61bddb0", size = 239500 },
+ { url = "https://files.pythonhosted.org/packages/76/74/5d90ad14d55fbe3f9c474fdcb6e34b4bed99e3be8efac98734a5ddce88c1/anthropic-0.49.0-py3-none-any.whl", hash = "sha256:bbc17ad4e7094988d2fa86b87753ded8dce12498f4b85fe5810f208f454a8375", size = 243368 },
]
[[package]]
@@ -449,7 +449,7 @@ typing = [
[package.metadata]
requires-dist = [
- { name = "anthropic", specifier = ">=0.47.0,<1" },
+ { name = "anthropic", specifier = ">=0.49.0,<1" },
{ name = "langchain-core", editable = "../../core" },
{ name = "pydantic", specifier = ">=2.7.4,<3.0.0" },
]
@@ -483,7 +483,7 @@ typing = [
[[package]]
name = "langchain-core"
-version = "0.3.41"
+version = "0.3.45rc1"
source = { editable = "../../core" }
dependencies = [
{ name = "jsonpatch" },
@@ -541,7 +541,7 @@ typing = [
[[package]]
name = "langchain-tests"
-version = "0.3.12"
+version = "0.3.14"
source = { editable = "../../standard-tests" }
dependencies = [
{ name = "httpx" },