uipath-langchain 0.0.112__py3-none-any.whl → 0.1.24__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- uipath_langchain/_cli/_templates/main.py.template +12 -13
- uipath_langchain/_cli/cli_init.py +127 -156
- uipath_langchain/_cli/cli_new.py +2 -6
- uipath_langchain/_resources/AGENTS.md +21 -0
- uipath_langchain/_resources/REQUIRED_STRUCTURE.md +92 -0
- uipath_langchain/{tracers → _tracing}/__init__.py +0 -2
- uipath_langchain/_tracing/_instrument_traceable.py +134 -0
- uipath_langchain/_utils/__init__.py +1 -2
- uipath_langchain/_utils/_request_mixin.py +351 -54
- uipath_langchain/_utils/_settings.py +2 -11
- uipath_langchain/agent/exceptions/__init__.py +6 -0
- uipath_langchain/agent/exceptions/exceptions.py +11 -0
- uipath_langchain/agent/guardrails/__init__.py +21 -0
- uipath_langchain/agent/guardrails/actions/__init__.py +11 -0
- uipath_langchain/agent/guardrails/actions/base_action.py +23 -0
- uipath_langchain/agent/guardrails/actions/block_action.py +41 -0
- uipath_langchain/agent/guardrails/actions/escalate_action.py +274 -0
- uipath_langchain/agent/guardrails/actions/log_action.py +57 -0
- uipath_langchain/agent/guardrails/guardrail_nodes.py +125 -0
- uipath_langchain/agent/guardrails/guardrails_factory.py +70 -0
- uipath_langchain/agent/guardrails/guardrails_subgraph.py +247 -0
- uipath_langchain/agent/guardrails/types.py +20 -0
- uipath_langchain/agent/react/__init__.py +14 -0
- uipath_langchain/agent/react/agent.py +113 -0
- uipath_langchain/agent/react/constants.py +2 -0
- uipath_langchain/agent/react/init_node.py +20 -0
- uipath_langchain/agent/react/llm_node.py +43 -0
- uipath_langchain/agent/react/router.py +97 -0
- uipath_langchain/agent/react/terminate_node.py +82 -0
- uipath_langchain/agent/react/tools/__init__.py +7 -0
- uipath_langchain/agent/react/tools/tools.py +50 -0
- uipath_langchain/agent/react/types.py +39 -0
- uipath_langchain/agent/react/utils.py +49 -0
- uipath_langchain/agent/tools/__init__.py +17 -0
- uipath_langchain/agent/tools/context_tool.py +53 -0
- uipath_langchain/agent/tools/escalation_tool.py +111 -0
- uipath_langchain/agent/tools/integration_tool.py +181 -0
- uipath_langchain/agent/tools/process_tool.py +49 -0
- uipath_langchain/agent/tools/static_args.py +138 -0
- uipath_langchain/agent/tools/structured_tool_with_output_type.py +14 -0
- uipath_langchain/agent/tools/tool_factory.py +45 -0
- uipath_langchain/agent/tools/tool_node.py +22 -0
- uipath_langchain/agent/tools/utils.py +11 -0
- uipath_langchain/chat/__init__.py +4 -0
- uipath_langchain/chat/bedrock.py +187 -0
- uipath_langchain/chat/gemini.py +330 -0
- uipath_langchain/chat/mapper.py +309 -0
- uipath_langchain/chat/models.py +261 -38
- uipath_langchain/chat/openai.py +132 -0
- uipath_langchain/chat/supported_models.py +42 -0
- uipath_langchain/embeddings/embeddings.py +136 -36
- uipath_langchain/middlewares.py +0 -2
- uipath_langchain/py.typed +0 -0
- uipath_langchain/retrievers/context_grounding_retriever.py +7 -9
- uipath_langchain/runtime/__init__.py +36 -0
- uipath_langchain/runtime/_serialize.py +46 -0
- uipath_langchain/runtime/config.py +61 -0
- uipath_langchain/runtime/errors.py +43 -0
- uipath_langchain/runtime/factory.py +315 -0
- uipath_langchain/runtime/graph.py +159 -0
- uipath_langchain/runtime/runtime.py +453 -0
- uipath_langchain/runtime/schema.py +349 -0
- uipath_langchain/runtime/storage.py +115 -0
- uipath_langchain/vectorstores/context_grounding_vectorstore.py +90 -110
- {uipath_langchain-0.0.112.dist-info → uipath_langchain-0.1.24.dist-info}/METADATA +42 -20
- uipath_langchain-0.1.24.dist-info/RECORD +76 -0
- {uipath_langchain-0.0.112.dist-info → uipath_langchain-0.1.24.dist-info}/WHEEL +1 -1
- uipath_langchain-0.1.24.dist-info/entry_points.txt +5 -0
- uipath_langchain/_cli/_runtime/_context.py +0 -21
- uipath_langchain/_cli/_runtime/_exception.py +0 -17
- uipath_langchain/_cli/_runtime/_input.py +0 -136
- uipath_langchain/_cli/_runtime/_output.py +0 -234
- uipath_langchain/_cli/_runtime/_runtime.py +0 -371
- uipath_langchain/_cli/_utils/_graph.py +0 -202
- uipath_langchain/_cli/cli_run.py +0 -80
- uipath_langchain/tracers/AsyncUiPathTracer.py +0 -274
- uipath_langchain/tracers/_events.py +0 -33
- uipath_langchain/tracers/_instrument_traceable.py +0 -416
- uipath_langchain/tracers/_utils.py +0 -52
- uipath_langchain-0.0.112.dist-info/RECORD +0 -36
- uipath_langchain-0.0.112.dist-info/entry_points.txt +0 -2
- {uipath_langchain-0.0.112.dist-info → uipath_langchain-0.1.24.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,309 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import logging
|
|
3
|
+
from datetime import datetime, timezone
|
|
4
|
+
from typing import Any, cast
|
|
5
|
+
from uuid import uuid4
|
|
6
|
+
|
|
7
|
+
from langchain_core.messages import (
|
|
8
|
+
AIMessageChunk,
|
|
9
|
+
BaseMessage,
|
|
10
|
+
HumanMessage,
|
|
11
|
+
TextContentBlock,
|
|
12
|
+
ToolCallChunk,
|
|
13
|
+
ToolMessage,
|
|
14
|
+
)
|
|
15
|
+
from pydantic import ValidationError
|
|
16
|
+
from uipath.core.chat import (
|
|
17
|
+
UiPathConversationContentPartChunkEvent,
|
|
18
|
+
UiPathConversationContentPartEndEvent,
|
|
19
|
+
UiPathConversationContentPartEvent,
|
|
20
|
+
UiPathConversationContentPartStartEvent,
|
|
21
|
+
UiPathConversationMessage,
|
|
22
|
+
UiPathConversationMessageEndEvent,
|
|
23
|
+
UiPathConversationMessageEvent,
|
|
24
|
+
UiPathConversationMessageStartEvent,
|
|
25
|
+
UiPathConversationToolCallEndEvent,
|
|
26
|
+
UiPathConversationToolCallEvent,
|
|
27
|
+
UiPathConversationToolCallStartEvent,
|
|
28
|
+
UiPathInlineValue,
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
logger = logging.getLogger(__name__)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class UiPathChatMessagesMapper:
|
|
35
|
+
"""Stateful mapper that converts LangChain messages to UiPath message events.
|
|
36
|
+
|
|
37
|
+
Maintains state across multiple message conversions to properly track:
|
|
38
|
+
- The AI message ID associated with each tool call for proper correlation with ToolMessage
|
|
39
|
+
"""
|
|
40
|
+
|
|
41
|
+
def __init__(self):
|
|
42
|
+
"""Initialize the mapper with empty state."""
|
|
43
|
+
self.tool_call_to_ai_message: dict[str, str] = {}
|
|
44
|
+
self.seen_message_ids: set[str] = set()
|
|
45
|
+
|
|
46
|
+
def _extract_text(self, content: Any) -> str:
|
|
47
|
+
"""Normalize LangGraph message.content to plain text."""
|
|
48
|
+
if isinstance(content, str):
|
|
49
|
+
return content
|
|
50
|
+
if isinstance(content, list):
|
|
51
|
+
return "".join(
|
|
52
|
+
part.get("text", "")
|
|
53
|
+
for part in content
|
|
54
|
+
if isinstance(part, dict) and part.get("type") == "text"
|
|
55
|
+
)
|
|
56
|
+
return str(content or "")
|
|
57
|
+
|
|
58
|
+
def map_messages(self, messages: list[Any]) -> list[Any]:
|
|
59
|
+
"""Normalize any 'messages' list into LangChain messages.
|
|
60
|
+
|
|
61
|
+
- If already BaseMessage instances: return as-is.
|
|
62
|
+
- If UiPathConversationMessage: convert to HumanMessage.
|
|
63
|
+
"""
|
|
64
|
+
if not isinstance(messages, list):
|
|
65
|
+
raise TypeError("messages must be a list")
|
|
66
|
+
|
|
67
|
+
if not messages:
|
|
68
|
+
return []
|
|
69
|
+
|
|
70
|
+
first = messages[0]
|
|
71
|
+
|
|
72
|
+
# Case 1: already LangChain messages
|
|
73
|
+
if isinstance(first, BaseMessage):
|
|
74
|
+
return cast(list[BaseMessage], messages)
|
|
75
|
+
|
|
76
|
+
# Case 2: UiPath messages -> convert to HumanMessage
|
|
77
|
+
if isinstance(first, UiPathConversationMessage):
|
|
78
|
+
if not all(isinstance(m, UiPathConversationMessage) for m in messages):
|
|
79
|
+
raise TypeError("Mixed message types not supported")
|
|
80
|
+
return self._map_messages_internal(
|
|
81
|
+
cast(list[UiPathConversationMessage], messages)
|
|
82
|
+
)
|
|
83
|
+
|
|
84
|
+
# Case3: List[dict] -> parse to List[UiPathConversationMessage]
|
|
85
|
+
if isinstance(first, dict):
|
|
86
|
+
try:
|
|
87
|
+
parsed_messages = [
|
|
88
|
+
UiPathConversationMessage.model_validate(message)
|
|
89
|
+
for message in messages
|
|
90
|
+
]
|
|
91
|
+
return self._map_messages_internal(parsed_messages)
|
|
92
|
+
except ValidationError:
|
|
93
|
+
pass
|
|
94
|
+
|
|
95
|
+
# Fallback: unknown type – just pass through
|
|
96
|
+
return messages
|
|
97
|
+
|
|
98
|
+
def _map_messages_internal(
|
|
99
|
+
self, messages: list[UiPathConversationMessage]
|
|
100
|
+
) -> list[HumanMessage]:
|
|
101
|
+
"""
|
|
102
|
+
Converts a UiPathConversationMessage into a list of HumanMessages for LangGraph.
|
|
103
|
+
Supports multimodal content parts (text, external content) and preserves metadata.
|
|
104
|
+
"""
|
|
105
|
+
human_messages: list[HumanMessage] = []
|
|
106
|
+
|
|
107
|
+
for uipath_msg in messages:
|
|
108
|
+
# Loop over each content part
|
|
109
|
+
if uipath_msg.content_parts:
|
|
110
|
+
for part in uipath_msg.content_parts:
|
|
111
|
+
data = part.data
|
|
112
|
+
content = ""
|
|
113
|
+
metadata: dict[str, Any] = {
|
|
114
|
+
"message_id": uipath_msg.message_id,
|
|
115
|
+
"content_part_id": part.content_part_id,
|
|
116
|
+
"mime_type": part.mime_type,
|
|
117
|
+
"created_at": uipath_msg.created_at,
|
|
118
|
+
"updated_at": uipath_msg.updated_at,
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
if isinstance(data, UiPathInlineValue):
|
|
122
|
+
content = str(data.inline)
|
|
123
|
+
|
|
124
|
+
# Append a HumanMessage for this content part
|
|
125
|
+
human_messages.append(
|
|
126
|
+
HumanMessage(content=content, metadata=metadata)
|
|
127
|
+
)
|
|
128
|
+
|
|
129
|
+
# Handle the case where there are no content parts
|
|
130
|
+
else:
|
|
131
|
+
metadata = {
|
|
132
|
+
"message_id": uipath_msg.message_id,
|
|
133
|
+
"role": uipath_msg.role,
|
|
134
|
+
"created_at": uipath_msg.created_at,
|
|
135
|
+
"updated_at": uipath_msg.updated_at,
|
|
136
|
+
}
|
|
137
|
+
human_messages.append(HumanMessage(content="", metadata=metadata))
|
|
138
|
+
|
|
139
|
+
return human_messages
|
|
140
|
+
|
|
141
|
+
def map_event(
|
|
142
|
+
self,
|
|
143
|
+
message: BaseMessage,
|
|
144
|
+
) -> UiPathConversationMessageEvent | None:
|
|
145
|
+
"""Convert LangGraph BaseMessage (chunk or full) into a UiPathConversationMessageEvent.
|
|
146
|
+
|
|
147
|
+
Args:
|
|
148
|
+
message: The LangChain message to convert
|
|
149
|
+
|
|
150
|
+
Returns:
|
|
151
|
+
A UiPathConversationMessageEvent if the message should be emitted, None otherwise.
|
|
152
|
+
"""
|
|
153
|
+
# Format timestamp as ISO 8601 UTC with milliseconds: 2025-01-04T10:30:00.123Z
|
|
154
|
+
timestamp = (
|
|
155
|
+
datetime.now(timezone.utc)
|
|
156
|
+
.isoformat(timespec="milliseconds")
|
|
157
|
+
.replace("+00:00", "Z")
|
|
158
|
+
)
|
|
159
|
+
|
|
160
|
+
# --- Streaming AIMessageChunk ---
|
|
161
|
+
if isinstance(message, AIMessageChunk):
|
|
162
|
+
if message.id is None:
|
|
163
|
+
return None
|
|
164
|
+
|
|
165
|
+
msg_event = UiPathConversationMessageEvent(
|
|
166
|
+
message_id=message.id,
|
|
167
|
+
)
|
|
168
|
+
|
|
169
|
+
# Check if this is the last chunk by examining chunk_position
|
|
170
|
+
if message.chunk_position == "last":
|
|
171
|
+
msg_event.end = UiPathConversationMessageEndEvent(timestamp=timestamp)
|
|
172
|
+
msg_event.content_part = UiPathConversationContentPartEvent(
|
|
173
|
+
content_part_id=f"chunk-{message.id}-0",
|
|
174
|
+
end=UiPathConversationContentPartEndEvent(),
|
|
175
|
+
)
|
|
176
|
+
return msg_event
|
|
177
|
+
|
|
178
|
+
# For every new message_id, start a new message
|
|
179
|
+
if message.id not in self.seen_message_ids:
|
|
180
|
+
self.seen_message_ids.add(message.id)
|
|
181
|
+
msg_event.start = UiPathConversationMessageStartEvent(
|
|
182
|
+
role="assistant", timestamp=timestamp
|
|
183
|
+
)
|
|
184
|
+
msg_event.content_part = UiPathConversationContentPartEvent(
|
|
185
|
+
content_part_id=f"chunk-{message.id}-0",
|
|
186
|
+
start=UiPathConversationContentPartStartEvent(
|
|
187
|
+
mime_type="text/plain"
|
|
188
|
+
),
|
|
189
|
+
)
|
|
190
|
+
|
|
191
|
+
elif message.content_blocks:
|
|
192
|
+
for block in message.content_blocks:
|
|
193
|
+
block_type = block.get("type")
|
|
194
|
+
|
|
195
|
+
if block_type == "text":
|
|
196
|
+
text_block = cast(TextContentBlock, block)
|
|
197
|
+
text = text_block["text"]
|
|
198
|
+
|
|
199
|
+
msg_event.content_part = UiPathConversationContentPartEvent(
|
|
200
|
+
content_part_id=f"chunk-{message.id}-0",
|
|
201
|
+
chunk=UiPathConversationContentPartChunkEvent(
|
|
202
|
+
data=text,
|
|
203
|
+
content_part_sequence=0,
|
|
204
|
+
),
|
|
205
|
+
)
|
|
206
|
+
|
|
207
|
+
elif block_type == "tool_call_chunk":
|
|
208
|
+
tool_chunk_block = cast(ToolCallChunk, block)
|
|
209
|
+
|
|
210
|
+
tool_call_id = tool_chunk_block.get("id")
|
|
211
|
+
if tool_call_id:
|
|
212
|
+
# Track tool_call_id -> ai_message_id mapping
|
|
213
|
+
self.tool_call_to_ai_message[str(tool_call_id)] = message.id
|
|
214
|
+
|
|
215
|
+
args = tool_chunk_block.get("args") or ""
|
|
216
|
+
|
|
217
|
+
msg_event.content_part = UiPathConversationContentPartEvent(
|
|
218
|
+
content_part_id=f"chunk-{message.id}-0",
|
|
219
|
+
chunk=UiPathConversationContentPartChunkEvent(
|
|
220
|
+
data=args,
|
|
221
|
+
content_part_sequence=0,
|
|
222
|
+
),
|
|
223
|
+
)
|
|
224
|
+
# Continue so that multiple tool_call_chunks in the same block list
|
|
225
|
+
# are handled correctly
|
|
226
|
+
continue
|
|
227
|
+
|
|
228
|
+
# Fallback: raw string content on the chunk (rare when using content_blocks)
|
|
229
|
+
elif isinstance(message.content, str) and message.content:
|
|
230
|
+
msg_event.content_part = UiPathConversationContentPartEvent(
|
|
231
|
+
content_part_id=f"content-{message.id}",
|
|
232
|
+
chunk=UiPathConversationContentPartChunkEvent(
|
|
233
|
+
data=message.content,
|
|
234
|
+
content_part_sequence=0,
|
|
235
|
+
),
|
|
236
|
+
)
|
|
237
|
+
|
|
238
|
+
if (
|
|
239
|
+
msg_event.start
|
|
240
|
+
or msg_event.content_part
|
|
241
|
+
or msg_event.tool_call
|
|
242
|
+
or msg_event.end
|
|
243
|
+
):
|
|
244
|
+
return msg_event
|
|
245
|
+
|
|
246
|
+
return None
|
|
247
|
+
|
|
248
|
+
# --- ToolMessage ---
|
|
249
|
+
if isinstance(message, ToolMessage):
|
|
250
|
+
# Look up the AI message ID using the tool_call_id
|
|
251
|
+
result_message_id = (
|
|
252
|
+
self.tool_call_to_ai_message.get(message.tool_call_id)
|
|
253
|
+
if message.tool_call_id
|
|
254
|
+
else None
|
|
255
|
+
)
|
|
256
|
+
|
|
257
|
+
# If no AI message ID was found, we cannot properly associate this tool result
|
|
258
|
+
if not result_message_id:
|
|
259
|
+
logger.warning(
|
|
260
|
+
f"Tool message {message.tool_call_id} has no associated AI message ID. Skipping."
|
|
261
|
+
)
|
|
262
|
+
|
|
263
|
+
# Clean up the mapping after use
|
|
264
|
+
if (
|
|
265
|
+
message.tool_call_id
|
|
266
|
+
and message.tool_call_id in self.tool_call_to_ai_message
|
|
267
|
+
):
|
|
268
|
+
del self.tool_call_to_ai_message[message.tool_call_id]
|
|
269
|
+
|
|
270
|
+
content_value: Any = message.content
|
|
271
|
+
if isinstance(content_value, str):
|
|
272
|
+
try:
|
|
273
|
+
content_value = json.loads(content_value)
|
|
274
|
+
except (json.JSONDecodeError, TypeError):
|
|
275
|
+
# Keep as string if not valid JSON
|
|
276
|
+
pass
|
|
277
|
+
|
|
278
|
+
return UiPathConversationMessageEvent(
|
|
279
|
+
message_id=result_message_id or str(uuid4()),
|
|
280
|
+
tool_call=UiPathConversationToolCallEvent(
|
|
281
|
+
tool_call_id=message.tool_call_id,
|
|
282
|
+
start=UiPathConversationToolCallStartEvent(
|
|
283
|
+
tool_name=message.name,
|
|
284
|
+
arguments=None,
|
|
285
|
+
timestamp=timestamp,
|
|
286
|
+
),
|
|
287
|
+
end=UiPathConversationToolCallEndEvent(
|
|
288
|
+
timestamp=timestamp,
|
|
289
|
+
output=UiPathInlineValue(inline=content_value),
|
|
290
|
+
),
|
|
291
|
+
),
|
|
292
|
+
)
|
|
293
|
+
|
|
294
|
+
# --- Fallback for other BaseMessage types ---
|
|
295
|
+
text_content = self._extract_text(message.content)
|
|
296
|
+
return UiPathConversationMessageEvent(
|
|
297
|
+
message_id=message.id,
|
|
298
|
+
start=UiPathConversationMessageStartEvent(
|
|
299
|
+
role="assistant", timestamp=timestamp
|
|
300
|
+
),
|
|
301
|
+
content_part=UiPathConversationContentPartEvent(
|
|
302
|
+
content_part_id=f"cp-{message.id}",
|
|
303
|
+
chunk=UiPathConversationContentPartChunkEvent(data=text_content),
|
|
304
|
+
),
|
|
305
|
+
end=UiPathConversationMessageEndEvent(),
|
|
306
|
+
)
|
|
307
|
+
|
|
308
|
+
|
|
309
|
+
__all__ = ["UiPathChatMessagesMapper"]
|