Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[project]
name = "sap-cloud-sdk"
version = "0.27.0"
version = "0.27.1"
description = "SAP Cloud SDK for Python"
readme = "README.md"
license = "Apache-2.0"
Expand Down
5 changes: 4 additions & 1 deletion src/sap_cloud_sdk/agentgateway/converters.py
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,10 @@ async def run(**kwargs) -> str:

# Build args schema from input_schema
properties = mcp_tool.input_schema.get("properties", {})
fields: dict[str, Any] = {k: (str, ...) for k in properties}
required = set(mcp_tool.input_schema.get("required", []))
fields: dict[str, Any] = {
k: (str, ...) if k in required else (str | None, None) for k in properties
}
args_schema = create_model(f"{mcp_tool.name}_args", **fields) if fields else None

return StructuredTool.from_function(
Expand Down
28 changes: 28 additions & 0 deletions tests/agentgateway/unit/test_converters.py
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,34 @@ def test_handles_empty_input_schema(self):
assert result.name == "simple_tool"
assert result.args_schema is not None

def test_optional_fields_not_required_in_args_schema(self):
"""Fields absent from 'required' must be optional in the generated Pydantic model."""
tool = MCPTool(
name="get_supplier_bid",
server_name="ariba",
description="Gets all supplier bids for the specified event",
input_schema={
"type": "object",
"required": ["eventid"],
"properties": {
"eventid": {"description": "Unique identifier of the event"},
"showdeclinedreason": {"description": "Show supplier decline reason"},
"datafetchmode": {"description": "Level of detail for the response"},
},
},
url="https://example.com/mcp",
)

result = mcp_tool_to_langchain(tool, AsyncMock(return_value="result"), lambda: "token")

from pydantic import BaseModel

assert result.args_schema is not None and isinstance(result.args_schema, type) and issubclass(result.args_schema, BaseModel)
fields = result.args_schema.model_fields
assert fields["eventid"].is_required(), "eventid should be required"
assert not fields["showdeclinedreason"].is_required(), "showdeclinedreason should be optional"
assert not fields["datafetchmode"].is_required(), "datafetchmode should be optional"

def test_handles_input_schema_without_properties(self):
"""Handle MCPTool with input schema but no properties."""
tool = MCPTool(
Expand Down
6 changes: 3 additions & 3 deletions tests/core/integration/telemetry/_agent.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,9 @@ def build_langgraph_agent():
from typing import Annotated

from langchain_core.messages import BaseMessage
from langchain_litellm import ChatLiteLLM # ty: ignore[unresolved-import]
from langgraph.graph import END, StateGraph # ty: ignore[unresolved-import]
from langgraph.graph.message import add_messages # ty: ignore[unresolved-import]
from langchain_litellm import ChatLiteLLM
from langgraph.graph import END, StateGraph
from langgraph.graph.message import add_messages
except ImportError:
pytest.skip("langchain-litellm or langgraph not installed")

Expand Down
2 changes: 1 addition & 1 deletion tests/core/integration/telemetry/test_telemetry_bdd.py
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,7 @@ def _llm_call():
Uses ChatLiteLLM so Traceloop's LangChain instrumentor fires and emits a span
with gen_ai.usage.* token counts.
"""
from langchain_litellm import ChatLiteLLM # ty: ignore[unresolved-import]
from langchain_litellm import ChatLiteLLM
from langchain_core.messages import HumanMessage as LCHumanMessage
model_name = os.environ.get("AICORE_MODEL", "anthropic--claude-4.5-sonnet")
llm = ChatLiteLLM(model=f"sap/{model_name}")
Expand Down
Loading