Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
3b3fc87
feat: Add parallel tool calling support for Meta/Llama models
fede-kamel Oct 31, 2025
b6d8af7
Fix code formatting for line length compliance
fede-kamel Oct 31, 2025
18e375c
Update documentation to reflect broader model support for parallel to…
fede-kamel Oct 31, 2025
dac64db
Move integration test to correct folder structure
fede-kamel Nov 12, 2025
cd8080c
Add version filter for Llama parallel tool calling
fede-kamel Nov 12, 2025
03a2d5c
Fix linting issues after rebase
fede-kamel Nov 19, 2025
7708d9e
Fix remaining linting issues in test files
fede-kamel Nov 19, 2025
1c2b0ef
Move parallel tool call validation from bind_tools to provider
fede-kamel Nov 19, 2025
8b59fff
Add Llama 3.x validation at bind_tools time
fede-kamel Nov 19, 2025
1ed506a
Fix line length issue in bind_tools validation
fede-kamel Nov 24, 2025
3bb4d01
Apply ruff formatting to parallel tool calling tests
fede-kamel Nov 25, 2025
719516f
Move parallel_tool_calls to bind_tools only (remove class-level param)
fede-kamel Nov 25, 2025
b4bf03d
Update integration tests for bind_tools-only parallel_tool_calls
fede-kamel Nov 26, 2025
a1fde23
Fix README to show bind_tools-only parallel_tool_calls usage
fede-kamel Nov 26, 2025
296d719
Fix mypy type errors for LangChain 1.x compatibility
fede-kamel Nov 26, 2025
027be6a
Fix mypy errors for CI environment compatibility
fede-kamel Nov 26, 2025
24fdcfd
Fix Python 3.9 compatibility in test_oci_data_science.py
fede-kamel Nov 26, 2025
33e0e46
Simplify parallel tool calls: use provider property instead of model_…
fede-kamel Nov 26, 2025
d0d2c5d
Fix integration test for bind_tools validation timing
fede-kamel Nov 26, 2025
d17fc8f
Fix mypy linting issues for Python 3.9 compatibility
fede-kamel Nov 26, 2025
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
23 changes: 22 additions & 1 deletion libs/oci/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ embeddings.embed_query("What is the meaning of life?")
```

### 4. Use Structured Output
`ChatOCIGenAI` supports structured output.
`ChatOCIGenAI` supports structured output.

<sub>**Note:** The default method is `function_calling`. If default method returns `None` (e.g. for Gemini models), try `json_schema` or `json_mode`.</sub>

Expand Down Expand Up @@ -126,6 +126,27 @@ messages = [
response = client.invoke(messages)
```

### 6. Use Parallel Tool Calling (Meta/Llama 4+ models only)
Enable parallel tool calling to execute multiple tools simultaneously, improving performance for multi-tool workflows.

```python
from langchain_oci import ChatOCIGenAI

llm = ChatOCIGenAI(
model_id="meta.llama-4-maverick-17b-128e-instruct-fp8",
service_endpoint="https://inference.generativeai.us-chicago-1.oci.oraclecloud.com",
compartment_id="MY_COMPARTMENT_ID",
)

# Enable parallel tool calling in bind_tools
llm_with_tools = llm.bind_tools(
[get_weather, calculate_tip, get_population],
parallel_tool_calls=True # Tools can execute simultaneously
)
```

<sub>**Note:** Parallel tool calling is only supported for Llama 4+ models. Llama 3.x (including 3.3) and Cohere models will raise an error if this parameter is used.</sub>


## OCI Data Science Model Deployment Examples

Expand Down
77 changes: 61 additions & 16 deletions libs/oci/langchain_oci/chat_models/oci_generative_ai.py
Original file line number Diff line number Diff line change
Expand Up @@ -209,6 +209,18 @@ def process_stream_tool_calls(
"""Process streaming tool calls from event data into chunks."""
...

@property
def supports_parallel_tool_calls(self) -> bool:
"""Whether this provider supports parallel tool calling.

Parallel tool calling allows the model to call multiple tools
simultaneously in a single response.

Returns:
bool: True if parallel tool calling is supported, False otherwise.
"""
return False


class CohereProvider(Provider):
"""Provider implementation for Cohere."""
Expand Down Expand Up @@ -363,6 +375,14 @@ def messages_to_oci_params(

This includes conversion of chat history and tool call results.
"""
# Cohere models don't support parallel tool calls
if kwargs.get("is_parallel_tool_calls"):
raise ValueError(
"Parallel tool calls are not supported for Cohere models. "
"This feature is only available for models using GenericChatRequest "
"(Meta, Llama, xAI Grok, OpenAI, Mistral)."
)

is_force_single_step = kwargs.get("is_force_single_step", False)
oci_chat_history = []

Expand Down Expand Up @@ -585,6 +605,11 @@ class GenericProvider(Provider):

stop_sequence_key: str = "stop"

@property
def supports_parallel_tool_calls(self) -> bool:
"""GenericProvider models support parallel tool calling."""
return True

def __init__(self) -> None:
from oci.generative_ai_inference import models

Expand Down Expand Up @@ -851,6 +876,10 @@ def _should_allow_more_tool_calls(
result["tool_choice"] = self.oci_tool_choice_none()
# else: Allow model to decide (default behavior)

# Add parallel tool calls support (GenericChatRequest models)
if "is_parallel_tool_calls" in kwargs:
result["is_parallel_tool_calls"] = kwargs["is_parallel_tool_calls"]

return result

def _process_message_content(
Expand Down Expand Up @@ -916,23 +945,9 @@ def convert_to_oci_tool(
Raises:
ValueError: If the tool type is not supported.
"""
if (isinstance(tool, type) and issubclass(tool, BaseModel)) or callable(tool):
as_json_schema_function = convert_to_openai_function(tool)
parameters = as_json_schema_function.get("parameters", {})
# Check BaseTool first since it's callable but needs special handling
if isinstance(tool, BaseTool):
return self.oci_function_definition(
name=as_json_schema_function.get("name"),
description=as_json_schema_function.get(
"description",
as_json_schema_function.get("name"),
),
parameters={
"type": "object",
"properties": parameters.get("properties", {}),
"required": parameters.get("required", []),
},
)
elif isinstance(tool, BaseTool): # type: ignore[unreachable]
return self.oci_function_definition( # type: ignore[unreachable]
name=tool.name,
description=OCIUtils.remove_signature_from_tool_description(
tool.name, tool.description
Expand All @@ -953,6 +968,21 @@ def convert_to_oci_tool(
],
},
)
if (isinstance(tool, type) and issubclass(tool, BaseModel)) or callable(tool):
as_json_schema_function = convert_to_openai_function(tool)
parameters = as_json_schema_function.get("parameters", {})
return self.oci_function_definition(
name=as_json_schema_function.get("name"),
description=as_json_schema_function.get(
"description",
as_json_schema_function.get("name"),
),
parameters={
"type": "object",
"properties": parameters.get("properties", {}),
"required": parameters.get("required", []),
},
)
raise ValueError(
f"Unsupported tool type {type(tool)}. "
"Tool must be passed in as a BaseTool "
Expand Down Expand Up @@ -1211,6 +1241,7 @@ def bind_tools(
tool_choice: Optional[
Union[dict, str, Literal["auto", "none", "required", "any"], bool]
] = None,
parallel_tool_calls: Optional[bool] = None,
**kwargs: Any,
) -> Runnable[LanguageModelInput, BaseMessage]:
"""Bind tool-like objects to this chat model.
Expand All @@ -1231,6 +1262,11 @@ def bind_tools(
{"type": "function", "function": {"name": <<tool_name>>}}:
calls <<tool_name>> tool.
- False or None: no effect, default Meta behavior.
parallel_tool_calls: Whether to enable parallel function calling.
If True, the model can call multiple tools simultaneously.
If False or None (default), tools are called sequentially.
Supported for models using GenericChatRequest (Meta, xAI Grok,
OpenAI, Mistral). Not supported for Cohere models.
kwargs: Any additional parameters are passed directly to
:meth:`~langchain_oci.chat_models.oci_generative_ai.ChatOCIGenAI.bind`.
"""
Expand All @@ -1240,6 +1276,15 @@ def bind_tools(
if tool_choice is not None:
kwargs["tool_choice"] = self._provider.process_tool_choice(tool_choice)

# Add parallel tool calls support (only when explicitly enabled)
if parallel_tool_calls:
if not self._provider.supports_parallel_tool_calls:
raise ValueError(
"Parallel tool calls not supported for this provider. "
"Only GenericChatRequest models support parallel tool calling."
)
kwargs["is_parallel_tool_calls"] = True

return super().bind(tools=formatted_tools, **kwargs)

def with_structured_output(
Expand Down
Loading