Skip to content
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

Anthropic API error when using prebuilt create_react_agent after model ends turn with empty content message #3168

Closed
4 tasks done
maxmamis opened this issue Jan 23, 2025 · 8 comments

Comments

@maxmamis
Copy link

Checked other resources

  • This is a bug, not a usage question. For questions, please use GitHub Discussions.
  • I added a clear and detailed title that summarizes the issue.
  • I read what a minimal reproducible example is (https://stackoverflow.com/help/minimal-reproducible-example).
  • I included a self-contained, minimal example that demonstrates the issue INCLUDING all the relevant imports. The code run AS IS to reproduce the issue.

Example Code

import os
from typing import Literal
from dotenv import load_dotenv
from pprint import pprint

from langchain_core.tools import tool
from langgraph.checkpoint.memory import MemorySaver
from langchain_anthropic import ChatAnthropic
from langgraph.prebuilt import create_react_agent

load_dotenv()

@tool("NameThread")
def name_thread_tool(name: str):
    """
    Gives this message thread a nice name to show the customer. Keep it to a few words. Quick, witty, specific.
    """
    return name


prompt = """
You are a helpful assistant that can chat with a user.
You are communicating via text message, so make sure your responses are appropriate for that format: brief and VERY casual.

NAMING THE THREAD: 
Call the NameThread tool to give the thread a name to show the user. 
Make sure to ALWAYS give the thread a name after the very first message from the user!
As the user gives you more information and the conversation changes, make sure to update the thread name.
DO NOT continue responding to the user after naming the thread until they respond with a new message.
"""

tools = [name_thread_tool]
model = ChatAnthropic(
    model="claude-3-5-sonnet-latest",
    temperature=0,
)

memory = MemorySaver()
graph = create_react_agent(model, tools=tools, checkpointer=memory, state_modifier=prompt)
config = {"configurable": {"thread_id": "123"}}

pprint(graph.invoke({"messages": [("user", "Hi, I'm max.")]}, config))
print("\n##########################\n")
pprint(graph.invoke({"messages": [("user", "How are you doing?")]}, config))

Error Message and Stack Trace (if applicable)

Traceback (most recent call last):
  File "/Users/max/code/langchain_issue_demo/test.py", line 55, in <module>
    pprint(graph.invoke({"messages": [("user", "How are you doing?")]}, config))
  File "/Users/max/code/langchain_issue_demo/.venv/lib/python3.10/site-packages/langgraph/pregel/__init__.py", line 1961, in invoke
    for chunk in self.stream(
  File "/Users/max/code/langchain_issue_demo/.venv/lib/python3.10/site-packages/langgraph/pregel/__init__.py", line 1670, in stream
    for _ in runner.tick(
  File "/Users/max/code/langchain_issue_demo/.venv/lib/python3.10/site-packages/langgraph/pregel/runner.py", line 231, in tick
    run_with_retry(
  File "/Users/max/code/langchain_issue_demo/.venv/lib/python3.10/site-packages/langgraph/pregel/retry.py", line 40, in run_with_retry
    return task.proc.invoke(task.input, config)
  File "/Users/max/code/langchain_issue_demo/.venv/lib/python3.10/site-packages/langgraph/utils/runnable.py", line 462, in invoke
    input = step.invoke(input, config, **kwargs)
  File "/Users/max/code/langchain_issue_demo/.venv/lib/python3.10/site-packages/langgraph/utils/runnable.py", line 218, in invoke
    ret = context.run(self.func, *args, **kwargs)
  File "/Users/max/code/langchain_issue_demo/.venv/lib/python3.10/site-packages/langgraph/prebuilt/chat_agent_executor.py", line 628, in call_model
    response = model_runnable.invoke(state, config)
  File "/Users/max/code/langchain_issue_demo/.venv/lib/python3.10/site-packages/langchain_core/runnables/base.py", line 3022, in invoke
    input = context.run(step.invoke, input, config)
  File "/Users/max/code/langchain_issue_demo/.venv/lib/python3.10/site-packages/langchain_core/runnables/base.py", line 5352, in invoke
    return self.bound.invoke(
  File "/Users/max/code/langchain_issue_demo/.venv/lib/python3.10/site-packages/langchain_core/language_models/chat_models.py", line 286, in invoke
    self.generate_prompt(
  File "/Users/max/code/langchain_issue_demo/.venv/lib/python3.10/site-packages/langchain_core/language_models/chat_models.py", line 790, in generate_prompt
    return self.generate(prompt_messages, stop=stop, callbacks=callbacks, **kwargs)
  File "/Users/max/code/langchain_issue_demo/.venv/lib/python3.10/site-packages/langchain_core/language_models/chat_models.py", line 647, in generate
    raise e
  File "/Users/max/code/langchain_issue_demo/.venv/lib/python3.10/site-packages/langchain_core/language_models/chat_models.py", line 637, in generate
    self._generate_with_cache(
  File "/Users/max/code/langchain_issue_demo/.venv/lib/python3.10/site-packages/langchain_core/language_models/chat_models.py", line 855, in _generate_with_cache
    result = self._generate(
  File "/Users/max/code/langchain_issue_demo/.venv/lib/python3.10/site-packages/langchain_anthropic/chat_models.py", line 796, in _generate
    data = self._client.messages.create(**payload)
  File "/Users/max/code/langchain_issue_demo/.venv/lib/python3.10/site-packages/anthropic/_utils/_utils.py", line 275, in wrapper
    return func(*args, **kwargs)
  File "/Users/max/code/langchain_issue_demo/.venv/lib/python3.10/site-packages/anthropic/resources/messages/messages.py", line 904, in create
    return self._post(
  File "/Users/max/code/langchain_issue_demo/.venv/lib/python3.10/site-packages/anthropic/_base_client.py", line 1282, in post
    return cast(ResponseT, self.request(cast_to, opts, stream=stream, stream_cls=stream_cls))
  File "/Users/max/code/langchain_issue_demo/.venv/lib/python3.10/site-packages/anthropic/_base_client.py", line 959, in request
    return self._request(
  File "/Users/max/code/langchain_issue_demo/.venv/lib/python3.10/site-packages/anthropic/_base_client.py", line 1063, in _request
    raise self._make_status_error_from_response(err.response) from None
anthropic.BadRequestError: Error code: 400 - {'type': 'error', 'error': {'type': 'invalid_request_error', 'message': 'messages.3: all messages must have non-empty content except for the optional final assistant message'}}

Description

When using the prebuilt create_react_agent with Anthropic models, in some cases the agent will end a turn with a message with empty content. For example, my demo code produces the following output after the first user message:

{'messages': [HumanMessage(content="Hi, I'm max.", additional_kwargs={}, response_metadata={}, id='e869f686-d31a-4716-beed-c00608fd0469'),
              AIMessage(content=[{'text': 'Hey Max! Let me give this chat a name to start us off.', 'type': 'text'}, {'id': 'toolu_01PY3e9tqPGcCs7tfk5qV1C2', 'input': {'name': 'Meeting Max! 👋'}, 'name': 'NameThread', 'type': 'tool_use'}], additional_kwargs={}, response_metadata={'id': 'msg_011EGXw5nitFBxkmLN4NymqV', 'model': 'claude-3-5-sonnet-20241022', 'stop_reason': 'tool_use', 'stop_sequence': None, 'usage': {'cache_creation_input_tokens': 0, 'cache_read_input_tokens': 0, 'input_tokens': 527, 'output_tokens': 74}}, id='run-23cfb30e-7269-4ae3-a97e-45166f57c44f-0', tool_calls=[{'name': 'NameThread', 'args': {'name': 'Meeting Max! 👋'}, 'id': 'toolu_01PY3e9tqPGcCs7tfk5qV1C2', 'type': 'tool_call'}], usage_metadata={'input_tokens': 527, 'output_tokens': 74, 'total_tokens': 601, 'input_token_details': {'cache_read': 0, 'cache_creation': 0}}),
              ToolMessage(content='Meeting Max! 👋', name='NameThread', id='b37f9855-bc9d-450f-ab90-df49e5b91d55', tool_call_id='toolu_01PY3e9tqPGcCs7tfk5qV1C2'),
              AIMessage(content=[], additional_kwargs={}, response_metadata={'id': 'msg_01TeW2BazQH6PS7X7rbb6vEp', 'model': 'claude-3-5-sonnet-20241022', 'stop_reason': 'end_turn', 'stop_sequence': None, 'usage': {'cache_creation_input_tokens': 0, 'cache_read_input_tokens': 0, 'input_tokens': 618, 'output_tokens': 3}}, id='run-2a669331-b4c3-40fe-bb0e-b20736c45d94-0', usage_metadata={'input_tokens': 618, 'output_tokens': 3, 'total_tokens': 621, 'input_token_details': {'cache_read': 0, 'cache_creation': 0}})]}

Note the empty content in the final AIMessage. On the next turn, this message is included in the input to the agent, and causes Anthropic to return an error:

anthropic.BadRequestError: Error code: 400 - {'type': 'error', 'error': {'type': 'invalid_request_error', 'message': 'messages.3: all messages must have non-empty content except for the optional final assistant message'}}

I can filter these empty messages out manually, but I would expect this behavior to be included in the prebuilt workflow. Additionally, filtering out empty messages in this example will cause errors when using OpenAI models, so in order to be model-agnostic, I need to carefully special-case my handling.

Expected Result: No error, agent continues as normal
Observed Result: Error is thrown on any subsequent message sent to this thread

System Info

System Information

OS: Darwin
OS Version: Darwin Kernel Version 23.6.0: Wed Jul 31 20:48:04 PDT 2024; root:xnu-10063.141.1.700.5~1/RELEASE_ARM64_T6030
Python Version: 3.11.3 (main, Apr 7 2023, 21:05:46) [Clang 14.0.0 (clang-1400.0.29.202)]

Package Information

langchain_core: 0.3.31
langchain: 0.3.15
langchain_community: 0.3.15
langsmith: 0.3.1
langchain_anthropic: 0.3.3
langchain_openai: 0.3.1
langchain_text_splitters: 0.3.5
langgraph_sdk: 0.1.51

Optional packages not installed

langserve

Other Dependencies

aiohttp: 3.11.11
anthropic: 0.44.0
async-timeout: Installed. No version info available.
dataclasses-json: 0.6.7
defusedxml: 0.7.1
httpx: 0.28.1
httpx-sse: 0.4.0
jsonpatch: 1.33
langsmith-pyo3: Installed. No version info available.
numpy: 1.26.4
openai: 1.60.0
orjson: 3.10.15
packaging: 24.2
pydantic: 2.10.5
pydantic-settings: 2.7.1
pytest: Installed. No version info available.
PyYAML: 6.0.2
requests: 2.32.3
requests-toolbelt: 1.0.0
rich: Installed. No version info available.
SQLAlchemy: 2.0.37
tenacity: 9.0.0
tiktoken: 0.8.0
typing-extensions: 4.12.2
zstandard: 0.23.0

@gbaian10
Copy link
Contributor

When you send any empty string in messages to Anthropic, Anthropic will return a 400 error (anthropic.BadRequestError).

For example, both of the following cases will throw this error.

from dotenv import load_dotenv
from langchain_anthropic import ChatAnthropic

load_dotenv()
print(ChatAnthropic(model="claude-3-5-sonnet-latest").invoke(""))  # 400 error

messages = [
    ("human", "Hi, I'm Alice."),
    ("ai", ""),
    ("human", "How are you doing?"),
]

print(ChatAnthropic(model="claude-3-5-sonnet-latest").invoke(messages))  # 400 error

I think this issue is caused by the last sentence of your prompt, which seems to be a design flaw in the prompt.

Image

@maxmamis
Copy link
Author

@gbaian10 yes — i should have called this out in the original message. I think this issue is still a bug for the following reasons:

  1. In my actual use case, this is my desired behavior — I want the model to call the tool without writing additional text to the user. In my demo example, the alternative is that first it would respond "Hi Max!", then issue the NameThread tool call while also responding something like "I'm naming the thread now." — clearly undesirable. I want the tool call with no user-facing message content.
  2. Also in my production app, I don't have that explicit instruction in my prompt, but the model's behavior is the same. I only added the explicit instruction in order to reproduce the issue reliably in a minimal example.

@vbarda
Copy link
Collaborator

vbarda commented Jan 23, 2025

@maxmamis perhaps it would be sufficient to set @tool(return_direct=True) for your tool? this will stop the react loop in create_react_agent on the tool message from the tool that has return_direct=True and won't produce the final AI message

@maxmamis
Copy link
Author

@vbarda interesting, that might be what I'm looking for. thanks for the tip!

@vbarda
Copy link
Collaborator

vbarda commented Jan 24, 2025

@maxmamis great! could you please confirm that this resolves your issue and we can close this one then

@owais3901
Copy link

@vbarda Getting the same issue after setting the return_direct parameter.

@vbarda
Copy link
Collaborator

vbarda commented Jan 27, 2025

@owais3901 did you use the same example as above with return_direct=True for the tool? it works as expected for me. if not, can you provide an reproducible code example for your issue?

@vbarda
Copy link
Collaborator

vbarda commented Jan 31, 2025

Could folks please confirm if return_direct resolves the issue // provide more details? Otherwise will close this

@vbarda vbarda closed this as completed Feb 6, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants