Skip to content

Commit c380fd7

Browse files
fix: files messages stack
1 parent d87262f commit c380fd7

22 files changed

Lines changed: 183 additions & 82 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.21"
3+
version = "0.4.22"
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/multimodal/__init__.py renamed to src/uipath_langchain/agent/multimodal/__init__.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,18 @@
11
"""Multimodal LLM input handling (images, PDFs, etc.)."""
22

3-
from .invoke import build_file_content_block, llm_call_with_files
3+
from .invoke import (
4+
build_file_content_block,
5+
build_file_content_blocks,
6+
llm_call_with_files,
7+
)
48
from .types import IMAGE_MIME_TYPES, FileInfo
59
from .utils import download_file_base64, is_image, is_pdf, sanitize_filename
610

711
__all__ = [
812
"FileInfo",
913
"IMAGE_MIME_TYPES",
1014
"build_file_content_block",
15+
"build_file_content_blocks",
1116
"download_file_base64",
1217
"is_image",
1318
"is_pdf",

src/uipath_langchain/agent/react/multimodal/invoke.py renamed to src/uipath_langchain/agent/multimodal/invoke.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
"""LLM invocation with multimodal file attachments."""
22

3+
import asyncio
34
from typing import Any
45

56
from langchain_core.language_models import BaseChatModel
@@ -43,6 +44,24 @@ async def build_file_content_block(
4344
raise ValueError(f"Unsupported mime_type={file_info.mime_type}")
4445

4546

47+
async def build_file_content_blocks(files: list[FileInfo]) -> list[DataContentBlock]:
48+
"""Build content blocks from file attachments.
49+
50+
Args:
51+
files: List of file information to convert to content blocks
52+
53+
Returns:
54+
List of DataContentBlock instances for the files
55+
"""
56+
if not files:
57+
return []
58+
59+
file_content_blocks: list[DataContentBlock] = await asyncio.gather(
60+
*[build_file_content_block(file) for file in files]
61+
)
62+
return file_content_blocks
63+
64+
4665
async def llm_call_with_files(
4766
messages: list[AnyMessage],
4867
files: list[FileInfo],
File renamed without changes.
File renamed without changes.

src/uipath_langchain/agent/react/llm_node.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
from uipath_langchain.agent.tools.structured_tool_with_argument_properties import (
1515
StructuredToolWithArgumentProperties,
1616
)
17-
from uipath_langchain.llm import get_payload_handler
17+
from uipath_langchain.chat.handlers import get_payload_handler
1818

1919
from ..exceptions import AgentTerminationException
2020
from .constants import (

src/uipath_langchain/agent/tools/internal_tools/analyze_files_tool.py

Lines changed: 55 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,13 @@
22
from typing import Any
33

44
from langchain_core.language_models import BaseChatModel
5-
from langchain_core.messages import AnyMessage, HumanMessage, SystemMessage
5+
from langchain_core.messages import (
6+
AIMessage,
7+
AnyMessage,
8+
HumanMessage,
9+
SystemMessage,
10+
TextContentBlock,
11+
)
612
from langchain_core.messages.tool import ToolCall
713
from langchain_core.tools import BaseTool, StructuredTool
814
from uipath.agent.models.agent import (
@@ -11,18 +17,17 @@
1117
from uipath.eval.mocks import mockable
1218
from uipath.platform import UiPath
1319

20+
from uipath_langchain.agent.multimodal import FileInfo, build_file_content_blocks
1421
from uipath_langchain.agent.react.jsonschema_pydantic_converter import create_model
15-
from uipath_langchain.agent.react.multimodal import FileInfo, llm_call_with_files
1622
from uipath_langchain.agent.react.types import AgentGraphState
1723
from uipath_langchain.agent.tools.static_args import handle_static_args
1824
from uipath_langchain.agent.tools.structured_tool_with_argument_properties import (
1925
StructuredToolWithArgumentProperties,
2026
)
21-
from uipath_langchain.agent.tools.tool_node import (
22-
ToolWrapperReturnType,
23-
)
27+
from uipath_langchain.agent.tools.tool_node import ToolWrapperReturnType
2428
from uipath_langchain.agent.tools.utils import sanitize_tool_name
2529
from uipath_langchain.agent.wrappers import get_job_attachment_wrapper
30+
from uipath_langchain.chat.helpers import append_content_blocks_to_message
2631

2732
ANALYZE_FILES_SYSTEM_MESSAGE = (
2833
"Process the provided files to complete the given task. "
@@ -31,6 +36,41 @@
3136
)
3237

3338

39+
async def add_files_to_message(
40+
message: HumanMessage,
41+
files: list[FileInfo],
42+
) -> HumanMessage:
43+
"""Add file attachments to a HumanMessage."""
44+
if not files:
45+
return message
46+
47+
file_content_blocks = await build_file_content_blocks(files)
48+
return append_content_blocks_to_message(message, file_content_blocks)
49+
50+
51+
def extract_text_content(message: AIMessage) -> str:
52+
"""Extract text content from an AI message.
53+
Handles both simple string content and structured content with multiple parts extracting only the text portions.
54+
"""
55+
content = message.content_blocks
56+
57+
if isinstance(content, str):
58+
return content
59+
60+
if isinstance(content, list):
61+
text_parts: list[str] = []
62+
for part in content:
63+
if isinstance(part, TextContentBlock):
64+
if part.text:
65+
text_parts.append(part.text)
66+
elif isinstance(part, str):
67+
text_parts.append(part)
68+
69+
return "\n".join(text_parts)
70+
71+
return str(content)
72+
73+
3474
def create_analyze_file_tool(
3575
resource: AgentInternalToolResourceConfig, llm: BaseChatModel
3676
) -> StructuredTool:
@@ -50,8 +90,8 @@ async def tool_fn(**kwargs: Any):
5090
if "attachments" not in kwargs:
5191
raise ValueError("Argument 'attachments' is not available")
5292

53-
analysisTask = kwargs["analysisTask"]
54-
if not analysisTask:
93+
analysis_task = kwargs["analysisTask"]
94+
if not analysis_task:
5595
raise ValueError("Argument 'analysisTask' is not available")
5696

5797
attachments = kwargs["attachments"]
@@ -60,12 +100,17 @@ async def tool_fn(**kwargs: Any):
60100
if not files:
61101
return {"analysisResult": "No attachments provided to analyze."}
62102

103+
human_message = HumanMessage(content=analysis_task)
104+
human_message_with_files = await add_files_to_message(human_message, files)
105+
63106
messages: list[AnyMessage] = [
64107
SystemMessage(content=ANALYZE_FILES_SYSTEM_MESSAGE),
65-
HumanMessage(content=analysisTask),
108+
human_message_with_files,
66109
]
67-
result = await llm_call_with_files(messages, files, llm)
68-
return result
110+
result = await llm.ainvoke(messages)
111+
112+
analysis_result = extract_text_content(result)
113+
return analysis_result
69114

70115
job_attachment_wrapper = get_job_attachment_wrapper(output_type=output_model)
71116

src/uipath_langchain/llm/passthrough_factory.py renamed to src/uipath_langchain/chat/chat_model_factory.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ def _create_openai_llm(
3030
agenthub_config: str,
3131
byo_connection_id: str | None = None,
3232
) -> BaseChatModel:
33-
"""Create UiPathChatOpenAI for OpenAI models via passthrough."""
33+
"""Create UiPathChatOpenAI for OpenAI models via LLMGateway."""
3434
from uipath_langchain.chat.openai import UiPathChatOpenAI
3535

3636
azure_open_ai_latest_api_version = "2025-04-01-preview"
@@ -70,7 +70,7 @@ def _create_bedrock_llm(
7070
agenthub_config: str,
7171
byo_connection_id: str | None = None,
7272
) -> BaseChatModel:
73-
"""Create UiPathChatBedrockConverse for Claude models via passthrough."""
73+
"""Create UiPathChatBedrockConverse for Claude models via LLMGateway."""
7474
from uipath_langchain.chat.bedrock import (
7575
UiPathChatBedrock,
7676
UiPathChatBedrockConverse,
@@ -107,7 +107,7 @@ def _create_vertex_llm(
107107
agenthub_config: str,
108108
byo_connection_id: str | None = None,
109109
) -> BaseChatModel:
110-
"""Create UiPathChatVertex for Gemini models via passthrough."""
110+
"""Create UiPathChatVertex for Gemini models via LLMGateway."""
111111
from uipath_langchain.chat.vertex import UiPathChatVertex
112112

113113
match api_flavor:
@@ -185,7 +185,7 @@ def get_chat_model(
185185
agenthub_config: str,
186186
byo_connection_id: str | None = None,
187187
) -> BaseChatModel:
188-
"""Create and configure LLM instance using passthrough API.
188+
"""Create and configure LLM instance using LLMGateway API.
189189
190190
Fetches available models from the discovery API and selects the appropriate
191191
LLM class based on the apiFlavor field from the matching model configuration.

src/uipath_langchain/llm/handlers/__init__.py renamed to src/uipath_langchain/chat/handlers/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
from .base import ModelPayloadHandler
44
from .bedrock_converse import BedrockConversePayloadHandler
55
from .bedrock_invoke import BedrockInvokePayloadHandler
6+
from .handler_factory import get_payload_handler
67
from .openai_completions import OpenAICompletionsPayloadHandler
78
from .openai_responses import OpenAIResponsesPayloadHandler
89
from .vertex_gemini import VertexGeminiPayloadHandler
@@ -14,4 +15,5 @@
1415
"OpenAICompletionsPayloadHandler",
1516
"OpenAIResponsesPayloadHandler",
1617
"VertexGeminiPayloadHandler",
18+
"get_payload_handler",
1719
]
File renamed without changes.

0 commit comments

Comments
 (0)