From 878b257bd62c9a8a65fdb4fb25fe12707388ff8c Mon Sep 17 00:00:00 2001 From: Shawn Azman Date: Fri, 3 Apr 2026 16:43:09 +0000 Subject: [PATCH 1/3] fix: make langchain imports compatible with langchain v2 The langchain v2 package moved BaseMessage/HumanMessage from langchain.schema to langchain_core.messages. This caused agentlab-xray and other modules to crash with ModuleNotFoundError. Updated all three affected files to try langchain_core.messages first, fall back to langchain.schema, and gracefully handle the case where neither is available - matching the existing conditional import pattern used elsewhere in the codebase. Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> --- src/agentlab/analyze/agent_xray.py | 20 ++++++++++++++++---- src/agentlab/llm/llm_utils.py | 13 +++++++++++-- src/agentlab/llm/tracking.py | 6 +++++- 3 files changed, 32 insertions(+), 7 deletions(-) diff --git a/src/agentlab/analyze/agent_xray.py b/src/agentlab/analyze/agent_xray.py index 8accbfd6d..0e1ad043b 100644 --- a/src/agentlab/analyze/agent_xray.py +++ b/src/agentlab/analyze/agent_xray.py @@ -14,7 +14,15 @@ import pandas as pd from attr import dataclass from browsergym.experiments.loop import StepInfo as BGymStepInfo -from langchain.schema import BaseMessage, HumanMessage + +try: + from langchain_core.messages import BaseMessage, HumanMessage +except ImportError: + try: + from langchain.schema import BaseMessage, HumanMessage + except ImportError: + BaseMessage = None + HumanMessage = None from openai import OpenAI from openai.types.responses import ResponseFunctionToolCall from PIL import Image @@ -746,7 +754,7 @@ def format_chat_message(message: BaseMessage | MessageBuilder | dict): """ Format a message to markdown. """ - if isinstance(message, BaseMessage): + if BaseMessage is not None and isinstance(message, BaseMessage): return message.content elif isinstance(message, MessageBuilder): return message.to_markdown() @@ -847,8 +855,12 @@ def submit_action(input_text): global info agent_info = info.exp_result.steps_info[info.step].agent_info chat_messages = deepcopy(agent_info.get("chat_messages", ["No Chat Messages"])[:2]) - if isinstance(chat_messages[1], BaseMessage): # TODO remove once langchain is deprecated - assert isinstance(chat_messages[1], HumanMessage), "Second message should be user" + if BaseMessage is not None and isinstance( + chat_messages[1], BaseMessage + ): # TODO remove once langchain is deprecated + assert HumanMessage is not None and isinstance( + chat_messages[1], HumanMessage + ), "Second message should be user" chat_messages = [ make_system_message(chat_messages[0].content), make_user_message(chat_messages[1].content), diff --git a/src/agentlab/llm/llm_utils.py b/src/agentlab/llm/llm_utils.py index 10013b72a..7e5d78be3 100644 --- a/src/agentlab/llm/llm_utils.py +++ b/src/agentlab/llm/llm_utils.py @@ -22,8 +22,17 @@ langchain_community = importlib.util.find_spec("langchain_community") if langchain_community is not None: - from langchain.schema import BaseMessage as LangchainBaseMessage - from langchain_community.adapters.openai import convert_message_to_dict + try: + from langchain_core.messages import BaseMessage as LangchainBaseMessage + except ImportError: + try: + from langchain.schema import BaseMessage as LangchainBaseMessage + except ImportError: + LangchainBaseMessage = None + try: + from langchain_community.adapters.openai import convert_message_to_dict + except ImportError: + convert_message_to_dict = None else: LangchainBaseMessage = None convert_message_to_dict = None diff --git a/src/agentlab/llm/tracking.py b/src/agentlab/llm/tracking.py index 7b4b64f65..bc8e31715 100644 --- a/src/agentlab/llm/tracking.py +++ b/src/agentlab/llm/tracking.py @@ -13,7 +13,11 @@ langchain_community = importlib.util.find_spec("langchain_community") if langchain_community is not None: - from langchain_community.callbacks import bedrock_anthropic_callback, openai_info + try: + from langchain_community.callbacks import bedrock_anthropic_callback, openai_info + except ImportError: + bedrock_anthropic_callback = None + openai_info = None else: bedrock_anthropic_callback = None openai_info = None From 7eaaf9d779468f320272cfda93c75be1fe89678e Mon Sep 17 00:00:00 2001 From: Shawn Azman Date: Fri, 3 Apr 2026 16:51:21 +0000 Subject: [PATCH 2/3] fix: guard convert_message_to_dict against None when import fails independently Address Devin Review feedback: when langchain_core.messages is available but langchain_community.adapters.openai is not, convert_message_to_dict would be None. Added a None check with a fallback to manual dict construction. Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> --- src/agentlab/llm/llm_utils.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/agentlab/llm/llm_utils.py b/src/agentlab/llm/llm_utils.py index 7e5d78be3..72e3c3ed9 100644 --- a/src/agentlab/llm/llm_utils.py +++ b/src/agentlab/llm/llm_utils.py @@ -49,7 +49,10 @@ def messages_to_dict(messages: list[dict] | list[LangchainBaseMessage]) -> dict: elif isinstance(m, str): new_messages.add_message({"role": "", "content": m}) elif LangchainBaseMessage is not None and isinstance(m, LangchainBaseMessage): - new_messages.add_message(convert_message_to_dict(m)) + if convert_message_to_dict is not None: + new_messages.add_message(convert_message_to_dict(m)) + else: + new_messages.add_message({"role": "user", "content": str(m.content)}) else: raise ValueError(f"Unknown message type: {type(m)}") return new_messages From 03abaa5a5e721bd00d34b4d8234469dbe567f209 Mon Sep 17 00:00:00 2001 From: Shawn Azman Date: Fri, 3 Apr 2026 16:54:15 +0000 Subject: [PATCH 3/3] fix: use role_map for langchain message fallback instead of hardcoded 'user' MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Maps langchain message types (human→user, ai→assistant, system→system) to preserve correct roles when convert_message_to_dict is unavailable. Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> --- src/agentlab/llm/llm_utils.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/agentlab/llm/llm_utils.py b/src/agentlab/llm/llm_utils.py index 72e3c3ed9..eb2b7db53 100644 --- a/src/agentlab/llm/llm_utils.py +++ b/src/agentlab/llm/llm_utils.py @@ -52,7 +52,10 @@ def messages_to_dict(messages: list[dict] | list[LangchainBaseMessage]) -> dict: if convert_message_to_dict is not None: new_messages.add_message(convert_message_to_dict(m)) else: - new_messages.add_message({"role": "user", "content": str(m.content)}) + role_map = {"human": "user", "ai": "assistant", "system": "system"} + new_messages.add_message( + {"role": role_map.get(m.type, m.type), "content": str(m.content)} + ) else: raise ValueError(f"Unknown message type: {type(m)}") return new_messages