Skip to content

botocore.errorfactory.ValidationException with Claude 3.5 models and below [Langchain-aws 0.2.15] #411

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

Open
sibijr opened this issue Mar 17, 2025 · 4 comments

Comments

@sibijr
Copy link

sibijr commented Mar 17, 2025

After upgrading to 0.2.15, I started seeing the below errors while working with Claude 3.5 or below models. The same code works fine with 3.7.

    | botocore.errorfactory.ValidationException: An error occurred (ValidationException) when calling the InvokeModelWithResponseStream operation: Malformed input request: #: subject must not be valid against schema {"required":["messages"]}#/messages/1/content: expected type: String, found: JSONArray#/messages/1/content: expected minimum item count: 1, found: 0, please reformat your input and try again.

Any help?

@3coins
Copy link
Collaborator

3coins commented Mar 17, 2025

@sibijr
Can you provide a small code sample to reproduce. Thanks.

@sibijr
Copy link
Author

sibijr commented Mar 23, 2025

Below the code sample to reproduce, It works with 3.7 but failed with 3.5. Please check

import json
from typing import Any, Dict, List, Optional, Tuple

import botocore
from langchain.agents import AgentExecutor
from langchain.agents.format_scratchpad import format_xml
from langchain_aws import ChatBedrock
from langchain_core.agents import AgentFinish
from langchain_core.messages import AIMessage, BaseMessage, HumanMessage, SystemMessage
from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder
from langchain_core.runnables import RunnableLambda
from pydantic import BaseModel, Field


# model_id = "us.anthropic.claude-3-7-sonnet-20250219-v1:0"
model_id = "us.anthropic.claude-3-5-sonnet-20240620-v1:0"


template = """
Below is the current codebase of the user: 

---------------------------------------

{codebase}

---------------------------------------
Codebase ends here. 

"""

conversational_prompt = ChatPromptTemplate.from_messages(
    [
        ("system", template),
        MessagesPlaceholder(variable_name="chat_history", optional=True),
        ("user", "{question}"),
        ("ai", "{agent_scratchpad}"),
    ]
)

def get_model_with_thinking(thinking_enabled=False):
    """
    Create a ChatBedrock model with thinking enabled or disabled based on UI toggle
    and model support
    """
    # Basic parameters for both model versions
    model_kwargs={"max_tokens": 8192, "temperature": 0.3, "top_k": 15}

    # Only add thinking parameter when explicitly enabled in UI AND using supported model
    if thinking_enabled and "claude-3-7" in model_id:
        model_kwargs= {
            "max_tokens": 8192,
            "temperature": 1, # temperature has to be set as 1
            "thinking": {
                "type": "enabled",
                "budget_tokens": 4024,
            }
        }
        
    # Add anthropic_version parameter for Claude 3.5
    if "claude-3-5" in model_id:
        model_kwargs["anthropic_version"] = "bedrock-2023-05-31"

    return ChatBedrock(
        model_id=model_id,
        model_kwargs=model_kwargs,
        credentials_profile_name='spyziya',
        config=botocore.config.Config(
            read_timeout=900
        )
    )

def parse_output(message):
    """
    Parse and sanitize the output from the language model.
    This function converts the content into a string before returning,
    ensuring that the UI receives a proper text output.
    
    Handles different message content formats between Claude 3.5 and 3.7.
    """
    content = message.content

    # If the content is a list, join the text from each object.
    if isinstance(content, list):
        text_parts = []
        for item in content:
            if isinstance(item, dict):
                # Extract the 'text' field if available; otherwise, serialize the dict.
                text_parts.append(item.get("text", json.dumps(item)))
            else:
                text_parts.append(str(item))
        final_text = " ".join(text_parts)
    else:
        final_text = str(content)
        

    return AgentFinish(return_values={"output": final_text}, log=final_text)

def agent(input_data):
    print(f"Input: {input_data}")
    # Create the LLM with thinking mode based on the config.
    # Only enable thinking for Claude 3.7
    thinking_enabled = input_data["config"].get("thinking", False) and "claude-3-7" in model_id
    llm_bound = get_model_with_thinking(thinking_enabled)

    # Format the agent scratchpad differently based on the model
    format_scratchpad = lambda x: format_xml(x["intermediate_steps"])
    
    return (
            {
                "codebase": lambda x: "Code base here",
                "question": lambda x: x["question"],
                "agent_scratchpad": format_scratchpad,
                "chat_history": lambda x: ["Chat History"],
            }
            | conversational_prompt
            | llm_bound
            | parse_output
    )


class AgentInput(BaseModel):
    question: str
    config: dict = Field({})
    chat_history: List[Tuple[str, str]] = Field(..., extra={"widget": {"type": "chat"}})


agent_executor = AgentExecutor(
    agent=RunnableLambda(lambda x: agent(x)), tools=[], verbose=True, handle_parsing_errors=True
).with_types(input_type=AgentInput)

agent_executor = agent_executor | (lambda x: x["output"])


question = "what is the capital of india?"
print(agent_executor.invoke({"question": question, "chat_history": [], "config": {}}))

@sibijr
Copy link
Author

sibijr commented Mar 23, 2025

@3coins

@3coins
Copy link
Collaborator

3coins commented Mar 25, 2025

@sibijr
Thanks for providing the sample code. We will take a look at this in our next sprint.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants