From 59737946df4b0153e602d7a8bcdc2a2ff4d72964 Mon Sep 17 00:00:00 2001 From: Mateusz Szewczyk Date: Thu, 13 Nov 2025 10:15:48 +0100 Subject: [PATCH 1/7] chore(langchain-ibm): Update chat_models to support granite with Langchain --- libs/ibm/langchain_ibm/chat_models.py | 40 +++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/libs/ibm/langchain_ibm/chat_models.py b/libs/ibm/langchain_ibm/chat_models.py index 8271554..9e4a15a 100644 --- a/libs/ibm/langchain_ibm/chat_models.py +++ b/libs/ibm/langchain_ibm/chat_models.py @@ -1,5 +1,6 @@ """IBM watsonx.ai chat wrapper.""" +import ast import hashlib import json import logging @@ -90,6 +91,38 @@ logger = logging.getLogger(__name__) +def normalize_tool_arguments(args_str: str) -> str: + """Ensure arguments is always a proper JSON string. + + Handles: + - JSON string + - Python dict string + - Extra wrapping quotes like '"{...}"' + Args: + args_str: tool call args_str. + + Returns: + The LangChain tool call arguments args_str. + """ + # Step 1: Remove outer quotes if it's a string starting and ending with quotes + if ( + isinstance(args_str, str) + and args_str.startswith('"') + and args_str.endswith('"') + ): + args_str = args_str[1:-1].encode().decode("unicode_escape") + + # Step 2: Try JSON + try: + parsed = json.loads(args_str) + except json.JSONDecodeError: + # Step 3: If JSON fails, try Python dict string + parsed = ast.literal_eval(args_str) + + # Step 4: Convert back to JSON string + return json.dumps(parsed, indent=2) + + def _convert_dict_to_message(_dict: Mapping[str, Any], call_id: str) -> BaseMessage: """Convert a dictionary to a LangChain message. @@ -117,6 +150,13 @@ def _convert_dict_to_message(_dict: Mapping[str, Any], call_id: str) -> BaseMess if raw_tool_calls := _dict.get("tool_calls"): additional_kwargs["tool_calls"] = raw_tool_calls for raw_tool_call in raw_tool_calls: + ## Code change to support langgraph with A2A and graph.astream. + if "function" in raw_tool_call: + func = raw_tool_call.get("function", {}) + if "arguments" in func: + raw_args = raw_tool_call["function"]["arguments"] + json_args_str = normalize_tool_arguments(raw_args) + raw_tool_call["function"]["arguments"] = json_args_str try: tool_calls.append(parse_tool_call(raw_tool_call, return_id=True)) except Exception as e: From ba161c0d068b6149a79b0da79c311e85cf89010d Mon Sep 17 00:00:00 2001 From: Mateusz Szewczyk Date: Thu, 13 Nov 2025 10:41:50 +0100 Subject: [PATCH 2/7] fix integration tests --- libs/ibm/tests/integration_tests/test_embeddings.py | 2 +- libs/ibm/tests/integration_tests/test_embeddings_standard.py | 2 +- libs/ibm/tests/integration_tests/test_rerank.py | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/libs/ibm/tests/integration_tests/test_embeddings.py b/libs/ibm/tests/integration_tests/test_embeddings.py index 31fa4e6..53d2d00 100644 --- a/libs/ibm/tests/integration_tests/test_embeddings.py +++ b/libs/ibm/tests/integration_tests/test_embeddings.py @@ -15,7 +15,7 @@ WX_PROJECT_ID = os.environ.get("WATSONX_PROJECT_ID", "") URL = "https://us-south.ml.cloud.ibm.com" -MODEL_ID = "ibm/slate-125m-english-rtrvr" +MODEL_ID = "ibm/slate-125m-english-rtrvr-v2" DOCUMENTS = ["What is a generative ai?", "What is a loan and how does it works?"] diff --git a/libs/ibm/tests/integration_tests/test_embeddings_standard.py b/libs/ibm/tests/integration_tests/test_embeddings_standard.py index 5f199a1..f6fabe9 100644 --- a/libs/ibm/tests/integration_tests/test_embeddings_standard.py +++ b/libs/ibm/tests/integration_tests/test_embeddings_standard.py @@ -9,7 +9,7 @@ URL = "https://us-south.ml.cloud.ibm.com" -MODEL_ID = "ibm/granite-embedding-107m-multilingual" +MODEL_ID = "ibm/granite-embedding-278m-multilingual" class TestWatsonxEmbeddingsStandard(EmbeddingsIntegrationTests): diff --git a/libs/ibm/tests/integration_tests/test_rerank.py b/libs/ibm/tests/integration_tests/test_rerank.py index 0c5dbb2..43d562c 100644 --- a/libs/ibm/tests/integration_tests/test_rerank.py +++ b/libs/ibm/tests/integration_tests/test_rerank.py @@ -14,7 +14,7 @@ URL = "https://us-south.ml.cloud.ibm.com" -MODEL_ID = "ibm/slate-125m-english-rtrvr" +MODEL_ID = "cross-encoder/ms-marco-minilm-l-12-v2" def test_01_rerank_init() -> None: @@ -47,7 +47,7 @@ def test_02_rerank_documents() -> None: def test_02_rerank_documents_with_params() -> None: - params = RerankParameters(truncate_input_tokens=1) + params = RerankParameters(truncate_input_tokens=10) test_documents = [ Document(page_content="This is a test document."), ] From a9d9fcb679aadbba51f286b056bf5ff6eed390b6 Mon Sep 17 00:00:00 2001 From: Mateusz Szewczyk Date: Thu, 13 Nov 2025 10:43:27 +0100 Subject: [PATCH 3/7] fix integration tests v2 --- libs/ibm/tests/integration_tests/test_rerank.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/libs/ibm/tests/integration_tests/test_rerank.py b/libs/ibm/tests/integration_tests/test_rerank.py index 43d562c..97355c4 100644 --- a/libs/ibm/tests/integration_tests/test_rerank.py +++ b/libs/ibm/tests/integration_tests/test_rerank.py @@ -14,8 +14,7 @@ URL = "https://us-south.ml.cloud.ibm.com" -MODEL_ID = "cross-encoder/ms-marco-minilm-l-12-v2" - +MODEL_ID = "ibm/slate-125m-english-rtrvr-v2" def test_01_rerank_init() -> None: wx_rerank = WatsonxRerank(model_id=MODEL_ID, url=URL, project_id=WX_PROJECT_ID) @@ -47,7 +46,7 @@ def test_02_rerank_documents() -> None: def test_02_rerank_documents_with_params() -> None: - params = RerankParameters(truncate_input_tokens=10) + params = RerankParameters(truncate_input_tokens=2) test_documents = [ Document(page_content="This is a test document."), ] From fd193a041ed4ec561ae858cd02052f85386eb9cf Mon Sep 17 00:00:00 2001 From: Mateusz Szewczyk Date: Thu, 13 Nov 2025 10:44:28 +0100 Subject: [PATCH 4/7] make format --- libs/ibm/tests/integration_tests/test_rerank.py | 1 + 1 file changed, 1 insertion(+) diff --git a/libs/ibm/tests/integration_tests/test_rerank.py b/libs/ibm/tests/integration_tests/test_rerank.py index 97355c4..3843bad 100644 --- a/libs/ibm/tests/integration_tests/test_rerank.py +++ b/libs/ibm/tests/integration_tests/test_rerank.py @@ -16,6 +16,7 @@ MODEL_ID = "ibm/slate-125m-english-rtrvr-v2" + def test_01_rerank_init() -> None: wx_rerank = WatsonxRerank(model_id=MODEL_ID, url=URL, project_id=WX_PROJECT_ID) assert isinstance(wx_rerank, WatsonxRerank) From b40caec40867bf36b3e7a409c5d2e0e2e3676edf Mon Sep 17 00:00:00 2001 From: Mateusz Szewczyk Date: Thu, 13 Nov 2025 11:31:18 +0100 Subject: [PATCH 5/7] patch version --- libs/ibm/pyproject.toml | 2 +- libs/ibm/uv.lock | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/libs/ibm/pyproject.toml b/libs/ibm/pyproject.toml index 32dff52..a83bd60 100644 --- a/libs/ibm/pyproject.toml +++ b/libs/ibm/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "pdm.backend" [project] name = "langchain-ibm" -version = "0.3.19.dev1" +version = "0.3.20" description = "An integration package connecting IBM watsonx.ai and LangChain" authors = [{ name = "IBM" }] license = { text = "MIT" } diff --git a/libs/ibm/uv.lock b/libs/ibm/uv.lock index 482f231..695bd07 100644 --- a/libs/ibm/uv.lock +++ b/libs/ibm/uv.lock @@ -428,7 +428,7 @@ wheels = [ [[package]] name = "langchain-ibm" -version = "0.3.19.dev1" +version = "0.3.20" source = { editable = "." } dependencies = [ { name = "ibm-watsonx-ai", version = "1.3.42", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, From 52297ea050890c586e29e639b4cc43829caf26b9 Mon Sep 17 00:00:00 2001 From: Mateusz Szewczyk Date: Thu, 13 Nov 2025 13:11:14 +0100 Subject: [PATCH 6/7] remove indent --- libs/ibm/langchain_ibm/chat_models.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libs/ibm/langchain_ibm/chat_models.py b/libs/ibm/langchain_ibm/chat_models.py index 9e4a15a..a2c06e6 100644 --- a/libs/ibm/langchain_ibm/chat_models.py +++ b/libs/ibm/langchain_ibm/chat_models.py @@ -120,7 +120,7 @@ def normalize_tool_arguments(args_str: str) -> str: parsed = ast.literal_eval(args_str) # Step 4: Convert back to JSON string - return json.dumps(parsed, indent=2) + return json.dumps(parsed) def _convert_dict_to_message(_dict: Mapping[str, Any], call_id: str) -> BaseMessage: From c8d71eddfbdd4de288d77e790860b993056a289a Mon Sep 17 00:00:00 2001 From: Mateusz Szewczyk Date: Thu, 13 Nov 2025 13:37:10 +0100 Subject: [PATCH 7/7] fixed normalize_tool_arguments function --- libs/ibm/langchain_ibm/chat_models.py | 25 +++++++++++-------------- 1 file changed, 11 insertions(+), 14 deletions(-) diff --git a/libs/ibm/langchain_ibm/chat_models.py b/libs/ibm/langchain_ibm/chat_models.py index a2c06e6..450763d 100644 --- a/libs/ibm/langchain_ibm/chat_models.py +++ b/libs/ibm/langchain_ibm/chat_models.py @@ -104,23 +104,20 @@ def normalize_tool_arguments(args_str: str) -> str: Returns: The LangChain tool call arguments args_str. """ - # Step 1: Remove outer quotes if it's a string starting and ending with quotes - if ( - isinstance(args_str, str) - and args_str.startswith('"') - and args_str.endswith('"') - ): - args_str = args_str[1:-1].encode().decode("unicode_escape") - - # Step 2: Try JSON + # Try to parse as JSON try: parsed = json.loads(args_str) except json.JSONDecodeError: - # Step 3: If JSON fails, try Python dict string - parsed = ast.literal_eval(args_str) - - # Step 4: Convert back to JSON string - return json.dumps(parsed) + pass + else: + if isinstance(parsed, str): + json.loads(parsed) + return parsed + return args_str + + # Try Python literal (e.g., "{'a': 1}") + obj: Any = ast.literal_eval(args_str) + return json.dumps(obj, ensure_ascii=False) def _convert_dict_to_message(_dict: Mapping[str, Any], call_id: str) -> BaseMessage: