Skip to content

Commit 26202ff

Browse files
feat: add file support for more LLM API Flavors
1 parent 7cfce44 commit 26202ff

22 files changed

Lines changed: 482 additions & 234 deletions

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[project]
22
name = "uipath-langchain"
3-
version = "0.4.9"
3+
version = "0.4.10"
44
description = "Python SDK that enables developers to build and deploy LangGraph agents to the UiPath Cloud Platform"
55
readme = { file = "README.md", content-type = "text/markdown" }
66
requires-python = ">=3.11"

src/uipath_langchain/agent/react/file_type_handler.py

Lines changed: 0 additions & 210 deletions
This file was deleted.

src/uipath_langchain/agent/react/llm_node.py

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
11
"""LLM node for ReAct Agent graph."""
22

3-
from typing import Literal, Sequence
3+
from typing import Any, Sequence
44

55
from langchain_core.language_models import BaseChatModel
66
from langchain_core.messages import AIMessage, AnyMessage
77
from langchain_core.tools import BaseTool
88

9+
from uipath_langchain.chat.types import APIFlavor
10+
911
from .constants import MAX_CONSECUTIVE_THINKING_MESSAGES
1012
from .types import AgentGraphState
1113
from .utils import count_consecutive_thinking_messages
@@ -21,15 +23,22 @@
2123

2224
def _get_required_tool_choice_by_model(
2325
model: BaseChatModel,
24-
) -> Literal["required", "any"]:
26+
) -> str | dict[str, Any]:
2527
"""Get the appropriate tool_choice value to enforce tool usage based on model type.
2628
27-
"required" - OpenAI compatible required tool_choice value
28-
"any" - Vertex and Bedrock parameter for required tool_choice value
29+
Returns:
30+
- "required" for OpenAI compatible models
31+
- "any" for Bedrock Converse and Vertex models (string format)
32+
- {"type": "any"} for Bedrock Invoke API (dict format required)
2933
"""
3034
model_class_name = model.__class__.__name__
3135
if model_class_name in OPENAI_COMPATIBLE_CHAT_MODELS:
3236
return "required"
37+
38+
api_flavor = getattr(model, "api_flavor", None)
39+
if api_flavor == APIFlavor.AWS_BEDROCK_INVOKE:
40+
return {"type": "any"}
41+
3342
return "any"
3443

3544

src/uipath_langchain/agent/react/llm_with_files.py

Lines changed: 12 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
from langchain_core.language_models import BaseChatModel
77
from langchain_core.messages import AIMessage, AnyMessage, HumanMessage
88

9-
from .file_type_handler import build_message_content_part_from_data
9+
from uipath_langchain.llm import get_content_builder
1010

1111

1212
@dataclass
@@ -18,29 +18,24 @@ class FileInfo:
1818
mime_type: str
1919

2020

21-
def _get_model_name(model: BaseChatModel) -> str:
22-
"""Extract model name from a BaseChatModel instance."""
23-
for attr in ["model_name", "_model_name", "model", "model_id"]:
24-
value = getattr(model, attr, None)
25-
if value and isinstance(value, str):
26-
return value
27-
raise ValueError(f"Model name not found in model {model}")
28-
29-
30-
async def create_part_for_file(
21+
async def build_file_content_part(
3122
file_info: FileInfo,
3223
model: BaseChatModel,
3324
) -> dict[str, Any]:
34-
"""Create a provider-specific message content part for a file attachment.
25+
"""Build a provider-specific message content part for a file attachment.
26+
27+
Args:
28+
file_info: File URL, name, and MIME type.
29+
model: The LLM model instance (must have llm_provider and api_flavor attributes).
3530
36-
Downloads the file from file_info.url and formats it for the model's provider.
31+
Returns:
32+
Provider-specific content part dictionary.
3733
"""
38-
model_name = _get_model_name(model)
39-
return await build_message_content_part_from_data(
34+
builder = get_content_builder(model)
35+
return await builder.build_file_content_part(
4036
url=file_info.url,
4137
filename=file_info.name,
4238
mime_type=file_info.mime_type,
43-
model=model_name,
4439
)
4540

4641

@@ -64,7 +59,7 @@ async def llm_call_with_files(
6459

6560
content_parts: list[str | dict[Any, Any]] = []
6661
for file_info in files:
67-
content_part = await create_part_for_file(file_info, model)
62+
content_part = await build_file_content_part(file_info, model)
6863
content_parts.append(content_part)
6964

7065
file_message = HumanMessage(content=content_parts)

src/uipath_langchain/chat/__init__.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,10 @@ def __getattr__(name):
2929
from . import supported_models
3030

3131
return getattr(supported_models, name)
32+
if name in ("LLMProvider", "APIFlavor"):
33+
from . import types
34+
35+
return getattr(types, name)
3236
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
3337

3438

@@ -39,4 +43,6 @@ def __getattr__(name):
3943
"OpenAIModels",
4044
"BedrockModels",
4145
"GeminiModels",
46+
"LLMProvider",
47+
"APIFlavor",
4248
]

src/uipath_langchain/chat/bedrock.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
from uipath.utils import EndpointManager
66

77
from .supported_models import BedrockModels
8+
from .types import APIFlavor, LLMProvider
89

910
logger = logging.getLogger(__name__)
1011

@@ -120,6 +121,9 @@ def _modify_request(self, request, **kwargs):
120121

121122

122123
class UiPathChatBedrockConverse(ChatBedrockConverse):
124+
llm_provider: LLMProvider = LLMProvider.BEDROCK
125+
api_flavor: APIFlavor = APIFlavor.AWS_BEDROCK_CONVERSE
126+
123127
def __init__(
124128
self,
125129
org_id: Optional[str] = None,
@@ -162,6 +166,9 @@ def __init__(
162166

163167

164168
class UiPathChatBedrock(ChatBedrock):
169+
llm_provider: LLMProvider = LLMProvider.BEDROCK
170+
api_flavor: APIFlavor = APIFlavor.AWS_BEDROCK_INVOKE
171+
165172
def __init__(
166173
self,
167174
org_id: Optional[str] = None,

src/uipath_langchain/chat/models.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,12 +21,17 @@
2121

2222
from uipath_langchain._utils._request_mixin import UiPathRequestMixin
2323

24+
from .types import APIFlavor, LLMProvider
25+
2426
logger = logging.getLogger(__name__)
2527

2628

2729
class UiPathAzureChatOpenAI(UiPathRequestMixin, AzureChatOpenAI):
2830
"""Custom LLM connector for LangChain integration with UiPath."""
2931

32+
llm_provider: LLMProvider = LLMProvider.OPENAI
33+
api_flavor: APIFlavor = APIFlavor.OPENAI_COMPLETIONS
34+
3035
def _generate(
3136
self,
3237
messages: list[BaseMessage],
@@ -169,6 +174,9 @@ def endpoint(self) -> str:
169174
class UiPathChat(UiPathRequestMixin, AzureChatOpenAI):
170175
"""Custom LLM connector for LangChain integration with UiPath Normalized."""
171176

177+
llm_provider: LLMProvider = LLMProvider.OPENAI
178+
api_flavor: APIFlavor = APIFlavor.OPENAI_COMPLETIONS
179+
172180
def _create_chat_result(
173181
self,
174182
response: Union[dict[str, Any], BaseModel],

0 commit comments

Comments
 (0)