langchain-ai / langchain-aws

Build LangChain Applications on AWS
MIT License
94 stars 68 forks source link

Converse operation: messages.2.content: Conversation blocks and tool result blocks cannot be provided in the same turn. #141

Open thiagotps opened 1 month ago

thiagotps commented 1 month ago

With the ChatBedrock class, the following code works as expected:

from langchain_core.prompts import ChatPromptTemplate
from langchain_aws import ChatBedrock
from langchain_core.output_parsers import StrOutputParser
from langchain_core.runnables import RunnablePassthrough
from langchain_core.messages import AIMessage, ToolMessage

def rag_query(query: str) -> str:
    """Do a RAG search in a internal knowledge base. """
    return "No results found !!!"

def web_query(query: str) -> str:
    """Search the web for the query."""
    return "Connection error"

prompt = ChatPromptTemplate.from_messages([
    ("system", "You are a helpful assistant with tools."),
    ("human", "Tell me who was Mozart ?"),
    AIMessage("", tool_calls=[{"name": "rag_query", "args": {"query": "Who was Mozart"}, "id": 1}]),
    ToolMessage("You correctly called the tool", tool_call_id="1"),
    ("human", "Who was Bethoveen ?"),
    AIMessage("", tool_calls=[{"name": "rag_query", "args": {"query": "Who was Bethoveen"}, "id": 2}]),
    ToolMessage("You correctly called the tool", tool_call_id="2"),
    ("human", "How to install linux ?"),
    AIMessage("", tool_calls=[{"name": "web_query", "args": {"query": "Tutorial on how to install Linux"}, "id": 3}]),
    ToolMessage("You correctly called the tool", tool_call_id="3"),
    ("human", "What is nmap"),
    AIMessage("", tool_calls=[{"name": "web_query", "args": {"query": "nmap documentation"}, "id": 4}]),
    ToolMessage("You correctly called the tool", tool_call_id="4"),
    ("human", "{question}")
])

bedrock = boto3.client('bedrock-runtime')
llm = ChatBedrock(model_id="anthropic.claude-3-haiku-20240307-v1:0", client=bedrock)

chain = {"question": RunnablePassthrough()}|  prompt | llm.bind_tools([rag_query, web_query])
chain.invoke("How to use docker ?")

Result:

AIMessage(content='', additional_kwargs={'usage': {'prompt_tokens': 741, 'completion_tokens': 54, 'total_tokens': 795}, 'stop_reason': 'tool_use', 'model_id': 'anthropic.claude-3-haiku-20240307-v1:0'}, response_metadata={'usage': {'prompt_tokens': 741, 'completion_tokens': 54, 'total_tokens': 795}, 'stop_reason': 'tool_use', 'model_id': 'anthropic.claude-3-haiku-20240307-v1:0'}, id='run-f7e8b75a-ceeb-4021-952d-39a74c880203-0', tool_calls=[{'name': 'web_query', 'args': {'query': 'docker tutorial'}, 'id': 'toolu_bdrk_01KGdyLm9JyGVVZQ4VQF1ZPR', 'type': 'tool_call'}], usage_metadata={'input_tokens': 741, 'output_tokens': 54, 'total_tokens': 795})

But when using the ChatBedrockConverse class:

from langchain_core.prompts import ChatPromptTemplate
from langchain_aws import ChatBedrockConverse
from langchain_core.output_parsers import StrOutputParser
from langchain_core.runnables import RunnablePassthrough
from langchain_core.messages import AIMessage, ToolMessage

def rag_query(query: str) -> str:
    """Do a RAG search in a internal knowledge base. """
    return "No results found !!!"

def web_query(query: str) -> str:
    """Search the web for the query."""
    return "Connection error"

prompt = ChatPromptTemplate.from_messages([
    ("system", "You are a helpful assistant with tools."),
    ("human", "Tell me who was Mozart ?"),
    AIMessage("", tool_calls=[{"name": "rag_query", "args": {"query": "Who was Mozart"}, "id": 1}]),
    ToolMessage("You correctly called the tool", tool_call_id="1"),
    ("human", "Who was Bethoveen ?"),
    AIMessage("", tool_calls=[{"name": "rag_query", "args": {"query": "Who was Bethoveen"}, "id": 2}]),
    ToolMessage("You correctly called the tool", tool_call_id="2"),
    ("human", "How to install linux ?"),
    AIMessage("", tool_calls=[{"name": "web_query", "args": {"query": "Tutorial on how to install Linux"}, "id": 3}]),
    ToolMessage("You correctly called the tool", tool_call_id="3"),
    ("human", "What is nmap"),
    AIMessage("", tool_calls=[{"name": "web_query", "args": {"query": "nmap documentation"}, "id": 4}]),
    ToolMessage("You correctly called the tool", tool_call_id="4"),
    ("human", "{question}")
])

bedrock = boto3.client('bedrock-runtime')
llm = ChatBedrockConverse(model_id="anthropic.claude-3-haiku-20240307-v1:0", client=bedrock)

chain = {"question": RunnablePassthrough()}|  prompt | llm.bind_tools([rag_query, web_query])
chain.invoke("How to use docker ?")

I receive the following exception:

ValidationException: An error occurred (ValidationException) when calling the Converse operation: messages.2.content: Conversation blocks and tool result blocks cannot be provided in the same turn.

@baskaryan

baskaryan commented 1 month ago

this is part of the anthropic API — you're not allowed to have adjacent Tool and Human messages, you need an AI message in between them. Not sure why ChatBedrock isn't raising an error here, will look into it.

If you add an AIMessage after your last ToolMessage and before the human message does that resolve the issue?

thiagotps commented 1 month ago

this is part of the anthropic API — you're not allowed to have adjacent Tool and Human messages, you need an AI message in between them. Not sure why ChatBedrock isn't raising an error here, will look into it.

If you add an AIMessage after your last ToolMessage and before the human message does that resolve the issue?

Adding the AIMessage after the last ToolMessage and before the human message didn't help:

from langchain_core.prompts import ChatPromptTemplate
from langchain_aws import ChatBedrockConverse
from langchain_core.output_parsers import StrOutputParser
from langchain_core.runnables import RunnablePassthrough
from langchain_core.messages import AIMessage, ToolMessage

def rag_query(query: str) -> str:
    """Do a RAG search in a internal knowledge base. """
    return "No results found !!!"

def web_query(query: str) -> str:
    """Search the web for the query."""
    return "Connection error"

prompt = ChatPromptTemplate.from_messages([
    ("system", "You are a helpful assistant with tools."),
    ("human", "Tell me who was Mozart ?"),
    AIMessage("", tool_calls=[{"name": "rag_query", "args": {"query": "Who was Mozart"}, "id": 1}]),
    ToolMessage("You correctly called the tool", tool_call_id="1"),
    ("human", "Who was Bethoveen ?"),
    AIMessage("", tool_calls=[{"name": "rag_query", "args": {"query": "Who was Bethoveen"}, "id": 2}]),
    ToolMessage("You correctly called the tool", tool_call_id="2"),
    ("human", "How to install linux ?"),
    AIMessage("", tool_calls=[{"name": "web_query", "args": {"query": "Tutorial on how to install Linux"}, "id": 3}]),
    ToolMessage("You correctly called the tool", tool_call_id="3"),
    ("human", "What is nmap"),
    AIMessage("", tool_calls=[{"name": "web_query", "args": {"query": "nmap documentation"}, "id": 4}]),
    ToolMessage("You correctly called the tool", tool_call_id="4"),
    AIMessage("Everything seems ok."),
    ("human", "{question}")
])

bedrock = boto3.client('bedrock-runtime')
llm = ChatBedrockConverse(model_id="anthropic.claude-3-haiku-20240307-v1:0", client=bedrock)

chain = {"question": RunnablePassthrough()}|  prompt | llm.bind_tools([rag_query, web_query])
chain.invoke("How to use docker ?")
ValidationException: An error occurred (ValidationException) when calling the Converse operation: messages.2.content: Conversation blocks and tool result blocks cannot be provided in the same turn.
baskaryan commented 1 month ago

this is part of the anthropic API — you're not allowed to have adjacent Tool and Human messages, you need an AI message in between them. Not sure why ChatBedrock isn't raising an error here, will look into it. If you add an AIMessage after your last ToolMessage and before the human message does that resolve the issue?

Adding the AIMessage after the last ToolMessage and before the human message didn't help:

from langchain_core.prompts import ChatPromptTemplate
from langchain_aws import ChatBedrockConverse
from langchain_core.output_parsers import StrOutputParser
from langchain_core.runnables import RunnablePassthrough
from langchain_core.messages import AIMessage, ToolMessage

def rag_query(query: str) -> str:
    """Do a RAG search in a internal knowledge base. """
    return "No results found !!!"

def web_query(query: str) -> str:
    """Search the web for the query."""
    return "Connection error"

prompt = ChatPromptTemplate.from_messages([
    ("system", "You are a helpful assistant with tools."),
    ("human", "Tell me who was Mozart ?"),
    AIMessage("", tool_calls=[{"name": "rag_query", "args": {"query": "Who was Mozart"}, "id": 1}]),
    ToolMessage("You correctly called the tool", tool_call_id="1"),
    ("human", "Who was Bethoveen ?"),
    AIMessage("", tool_calls=[{"name": "rag_query", "args": {"query": "Who was Bethoveen"}, "id": 2}]),
    ToolMessage("You correctly called the tool", tool_call_id="2"),
    ("human", "How to install linux ?"),
    AIMessage("", tool_calls=[{"name": "web_query", "args": {"query": "Tutorial on how to install Linux"}, "id": 3}]),
    ToolMessage("You correctly called the tool", tool_call_id="3"),
    ("human", "What is nmap"),
    AIMessage("", tool_calls=[{"name": "web_query", "args": {"query": "nmap documentation"}, "id": 4}]),
    ToolMessage("You correctly called the tool", tool_call_id="4"),
    AIMessage("Everything seems ok."),
    ("human", "{question}")
])

bedrock = boto3.client('bedrock-runtime')
llm = ChatBedrockConverse(model_id="anthropic.claude-3-haiku-20240307-v1:0", client=bedrock)

chain = {"question": RunnablePassthrough()}|  prompt | llm.bind_tools([rag_query, web_query])
chain.invoke("How to use docker ?")
ValidationException: An error occurred (ValidationException) when calling the Converse operation: messages.2.content: Conversation blocks and tool result blocks cannot be provided in the same turn.

ah sorry, didn't look closely. looks like you've got a number of adjacent tool/human messages. you'd actually need to do this for every tool message / human message adjacent pair. Screenshot 2024-08-05 at 5 42 46 PM

thiagotps commented 1 month ago

@baskaryan I made a test with _merge_messages from bedrock.py:

from langchain_core.messages import BaseMessage, HumanMessage, AIMessage, SystemMessage
from typing import Sequence, List, Union
def _merge_messages(
    messages: Sequence[BaseMessage],
) -> List[Union[SystemMessage, AIMessage, HumanMessage]]:
    """Merge runs of human/tool messages into single human messages with content blocks."""  # noqa: E501
    merged: list = []
    for curr in messages:
        curr = curr.copy(deep=True)
        if isinstance(curr, ToolMessage):
            if isinstance(curr.content, list) and all(
                isinstance(block, dict) and block.get("type") == "tool_result"
                for block in curr.content
            ):
                curr = HumanMessage(curr.content)  # type: ignore[misc]
            else:
                curr = HumanMessage(  # type: ignore[misc]
                    [
                        {
                            "type": "tool_result",
                            "content": curr.content,
                            "tool_use_id": curr.tool_call_id,
                        }
                    ]
                )
        last = merged[-1] if merged else None
        if isinstance(last, HumanMessage) and isinstance(curr, HumanMessage):
            if isinstance(last.content, str):
                new_content: List = [{"type": "text", "text": last.content}]
            else:
                new_content = last.content
            if isinstance(curr.content, str):
                new_content.append({"type": "text", "text": curr.content})
            else:
                new_content.extend(curr.content)
            last.content = new_content
        else:
            merged.append(curr)
    return merged

messages = [
    SystemMessage("You are a helpful assistant with tools."),
    HumanMessage("Tell me who was Mozart ?"),
    AIMessage("", tool_calls=[{"name": "rag_query", "args": {"query": "Who was Mozart"}, "id": 1}]),
    ToolMessage("You correctly called the tool", tool_call_id="1"),
    HumanMessage("Who was Bethoveen ?"),
    AIMessage("", tool_calls=[{"name": "rag_query", "args": {"query": "Who was Bethoveen"}, "id": 2}]),
    ToolMessage("You correctly called the tool", tool_call_id="2"),
    HumanMessage("How to install linux ?"),
    AIMessage("", tool_calls=[{"name": "web_query", "args": {"query": "Tutorial on how to install Linux"}, "id": 3}]),
    ToolMessage("You correctly called the tool", tool_call_id="3"),
    HumanMessage("What is nmap"),
    AIMessage("", tool_calls=[{"name": "web_query", "args": {"query": "nmap documentation"}, "id": 4}]),
    ToolMessage("You correctly called the tool", tool_call_id="4"),
    HumanMessage("{question}"),
]

_merge_messages(messages)

And the result was:

[SystemMessage(content='You are a helpful assistant with tools.'),
  HumanMessage(content='Tell me who was Mozart ?'),
  AIMessage(content='', tool_calls=[{'name': 'rag_query', 'args': {'query': 'Who was Mozart'}, 'id': '1', 'type': 'tool_call'}]),
  HumanMessage(content=[{'type': 'tool_result', 'content': 'You correctly called the tool', 'tool_use_id': '1'}, {'type': 'text', 'text': 'Who was Bethoveen ?'}]),
  AIMessage(content='', tool_calls=[{'name': 'rag_query', 'args': {'query': 'Who was Bethoveen'}, 'id': '2', 'type': 'tool_call'}]),
  HumanMessage(content=[{'type': 'tool_result', 'content': 'You correctly called the tool', 'tool_use_id': '2'}, {'type': 'text', 'text': 'How to install linux ?'}]),
  AIMessage(content='', tool_calls=[{'name': 'web_query', 'args': {'query': 'Tutorial on how to install Linux'}, 'id': '3', 'type': 'tool_call'}]),
  HumanMessage(content=[{'type': 'tool_result', 'content': 'You correctly called the tool', 'tool_use_id': '3'}, {'type': 'text', 'text': 'What is nmap'}]),
  AIMessage(content='', tool_calls=[{'name': 'web_query', 'args': {'query': 'nmap documentation'}, 'id': '4', 'type': 'tool_call'}]),
  HumanMessage(content=[{'type': 'tool_result', 'content': 'You correctly called the tool', 'tool_use_id': '4'}, {'type': 'text', 'text': '{question}'}])]

All intermediaries human questions were removed by the _merge_messages. It's probably the reason why ChatBedrock is not raising any error.