uipath-langchain 0.0.125__py3-none-any.whl → 0.0.127__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.

Potentially problematic release.


This version of uipath-langchain might be problematic. Click here for more details.

@@ -0,0 +1,298 @@
1
+ import uuid
2
+ from datetime import datetime
3
+ from typing import Any, Dict, List, Optional
4
+
5
+ from langchain_core.messages import (
6
+ AIMessage,
7
+ AIMessageChunk,
8
+ BaseMessage,
9
+ HumanMessage,
10
+ ToolMessage,
11
+ )
12
+ from uipath.agent.conversation import (
13
+ UiPathConversationContentPartChunkEvent,
14
+ UiPathConversationContentPartEndEvent,
15
+ UiPathConversationContentPartEvent,
16
+ UiPathConversationContentPartStartEvent,
17
+ UiPathConversationEvent,
18
+ UiPathConversationExchangeEvent,
19
+ UiPathConversationMessage,
20
+ UiPathConversationMessageEndEvent,
21
+ UiPathConversationMessageEvent,
22
+ UiPathConversationMessageStartEvent,
23
+ UiPathConversationToolCallEndEvent,
24
+ UiPathConversationToolCallEvent,
25
+ UiPathConversationToolCallStartEvent,
26
+ UiPathInlineValue,
27
+ )
28
+
29
+
30
+ def _new_id() -> str:
31
+ return str(uuid.uuid4())
32
+
33
+
34
+ def _wrap_in_conversation_event(
35
+ msg_event: UiPathConversationMessageEvent,
36
+ exchange_id: Optional[str] = None,
37
+ conversation_id: Optional[str] = None,
38
+ ) -> UiPathConversationEvent:
39
+ """Helper to wrap a message event into a conversation-level event."""
40
+ return UiPathConversationEvent(
41
+ conversation_id=conversation_id or _new_id(),
42
+ exchange=UiPathConversationExchangeEvent(
43
+ exchange_id=exchange_id or _new_id(),
44
+ message=msg_event,
45
+ ),
46
+ )
47
+
48
+
49
+ def _extract_text(content) -> str:
50
+ """Normalize LangGraph message.content to plain text."""
51
+ if isinstance(content, str):
52
+ return content
53
+ if isinstance(content, list):
54
+ return "".join(
55
+ part.get("text", "")
56
+ for part in content
57
+ if isinstance(part, dict) and part.get("type") == "text"
58
+ )
59
+ return str(content or "")
60
+
61
+
62
+ def uipath_to_human_messages(
63
+ uipath_msg: UiPathConversationMessage,
64
+ ) -> List[HumanMessage]:
65
+ """
66
+ Converts a UiPathConversationMessage into a list of HumanMessages for LangGraph.
67
+ Supports multimodal content parts (text, external content) and preserves metadata.
68
+ """
69
+ human_messages = []
70
+
71
+ # Loop over each content part
72
+ if uipath_msg.content_parts:
73
+ for part in uipath_msg.content_parts:
74
+ data = part.data
75
+ content = ""
76
+ metadata: Dict[str, Any] = {
77
+ "message_id": uipath_msg.message_id,
78
+ "content_part_id": part.content_part_id,
79
+ "mime_type": part.mime_type,
80
+ "created_at": uipath_msg.created_at,
81
+ "updated_at": uipath_msg.updated_at,
82
+ }
83
+
84
+ if isinstance(data, UiPathInlineValue):
85
+ content = str(data.inline)
86
+
87
+ # Append a HumanMessage for this content part
88
+ human_messages.append(HumanMessage(content=content, metadata=metadata))
89
+
90
+ # Handle the case where there are no content parts
91
+ else:
92
+ metadata = {
93
+ "message_id": uipath_msg.message_id,
94
+ "role": uipath_msg.role,
95
+ "created_at": uipath_msg.created_at,
96
+ "updated_at": uipath_msg.updated_at,
97
+ }
98
+ human_messages.append(HumanMessage(content="", metadata=metadata))
99
+
100
+ return human_messages
101
+
102
+
103
+ def map_message(
104
+ message: BaseMessage,
105
+ exchange_id: Optional[str] = None,
106
+ conversation_id: Optional[str] = None,
107
+ ) -> Optional[UiPathConversationEvent]:
108
+ """Convert LangGraph BaseMessage (chunk or full) into a UiPathConversationEvent."""
109
+ message_id = getattr(message, "id", None) or _new_id()
110
+ timestamp = datetime.now().isoformat()
111
+
112
+ # --- Streaming AIMessageChunk ---
113
+ if isinstance(message, AIMessageChunk):
114
+ msg_event = UiPathConversationMessageEvent(
115
+ message_id=message.id or _new_id(),
116
+ )
117
+
118
+ if message.content == []:
119
+ msg_event.start = UiPathConversationMessageStartEvent(
120
+ role="assistant", timestamp=timestamp
121
+ )
122
+ msg_event.content_part = UiPathConversationContentPartEvent(
123
+ content_part_id=f"chunk-{message.id}-{0}",
124
+ start=UiPathConversationContentPartStartEvent(mime_type="text/plain"),
125
+ )
126
+
127
+ elif isinstance(message.content, list) and message.content:
128
+ for chunk in message.content:
129
+ if not isinstance(chunk, dict):
130
+ continue
131
+ idx = chunk.get("index", 0)
132
+ ctype = chunk.get("type")
133
+ id = chunk.get("id", f"chunk-{message.id}-{idx}")
134
+
135
+ # Start of a tool call
136
+ if ctype == "tool_use":
137
+ msg_event.tool_call = UiPathConversationToolCallEvent(
138
+ tool_call_id=id,
139
+ start=UiPathConversationToolCallStartEvent(
140
+ tool_name=chunk.get("name") or "",
141
+ arguments=UiPathInlineValue(inline=""),
142
+ timestamp=timestamp,
143
+ ),
144
+ )
145
+
146
+ # JSON args streaming (content part for tool args)
147
+ elif ctype == "input_json_delta":
148
+ text = chunk.get("partial_json", "")
149
+ # first delta: emit content part start + first chunk
150
+ if text == "":
151
+ msg_event.content_part = UiPathConversationContentPartEvent(
152
+ content_part_id=id,
153
+ start=UiPathConversationContentPartStartEvent(
154
+ mime_type="application/json"
155
+ ),
156
+ )
157
+ else:
158
+ msg_event.content_part = UiPathConversationContentPartEvent(
159
+ content_part_id=id,
160
+ chunk=UiPathConversationContentPartChunkEvent(
161
+ data=text,
162
+ content_part_sequence=idx,
163
+ ),
164
+ )
165
+
166
+ # Plain text from assistant
167
+ elif ctype == "text":
168
+ text = chunk.get("text", "")
169
+ msg_event.content_part = UiPathConversationContentPartEvent(
170
+ content_part_id=id,
171
+ chunk=UiPathConversationContentPartChunkEvent(
172
+ data=text,
173
+ content_part_sequence=idx,
174
+ ),
175
+ )
176
+
177
+ stop_reason = message.response_metadata.get("stop_reason")
178
+ if not message.content and stop_reason in ("tool_use", "end_turn"):
179
+ msg_event.end = UiPathConversationMessageEndEvent(timestamp=timestamp)
180
+
181
+ if (
182
+ msg_event.start
183
+ or msg_event.content_part
184
+ or msg_event.tool_call
185
+ or msg_event.end
186
+ ):
187
+ return _wrap_in_conversation_event(msg_event, exchange_id, conversation_id)
188
+
189
+ return None
190
+
191
+ text_content = _extract_text(message.content)
192
+
193
+ # --- HumanMessage ---
194
+ if isinstance(message, HumanMessage):
195
+ return _wrap_in_conversation_event(
196
+ UiPathConversationMessageEvent(
197
+ message_id=message_id,
198
+ start=UiPathConversationMessageStartEvent(
199
+ role="user", timestamp=timestamp
200
+ ),
201
+ content_part=UiPathConversationContentPartEvent(
202
+ content_part_id=f"cp-{message_id}",
203
+ start=UiPathConversationContentPartStartEvent(
204
+ mime_type="text/plain"
205
+ ),
206
+ chunk=UiPathConversationContentPartChunkEvent(data=text_content),
207
+ end=UiPathConversationContentPartEndEvent(),
208
+ ),
209
+ end=UiPathConversationMessageEndEvent(),
210
+ ),
211
+ exchange_id,
212
+ conversation_id,
213
+ )
214
+
215
+ # --- AIMessage ---
216
+ if isinstance(message, AIMessage):
217
+ # Extract first tool call if present
218
+ tool_calls = getattr(message, "tool_calls", []) or []
219
+ first_tc = tool_calls[0] if tool_calls else None
220
+
221
+ return _wrap_in_conversation_event(
222
+ UiPathConversationMessageEvent(
223
+ message_id=message_id,
224
+ start=UiPathConversationMessageStartEvent(
225
+ role="assistant", timestamp=timestamp
226
+ ),
227
+ content_part=(
228
+ UiPathConversationContentPartEvent(
229
+ content_part_id=f"cp-{message_id}",
230
+ start=UiPathConversationContentPartStartEvent(
231
+ mime_type="text/plain"
232
+ ),
233
+ chunk=UiPathConversationContentPartChunkEvent(
234
+ data=text_content
235
+ ),
236
+ end=UiPathConversationContentPartEndEvent(),
237
+ )
238
+ if text_content
239
+ else None
240
+ ),
241
+ tool_call=(
242
+ UiPathConversationToolCallEvent(
243
+ tool_call_id=first_tc.get("id") or _new_id(),
244
+ start=UiPathConversationToolCallStartEvent(
245
+ tool_name=first_tc.get("name"),
246
+ arguments=UiPathInlineValue(
247
+ inline=str(first_tc.get("args", ""))
248
+ ),
249
+ timestamp=timestamp,
250
+ ),
251
+ )
252
+ if first_tc
253
+ else None
254
+ ),
255
+ end=UiPathConversationMessageEndEvent(),
256
+ ),
257
+ exchange_id,
258
+ conversation_id,
259
+ )
260
+
261
+ # --- ToolMessage ---
262
+ if isinstance(message, ToolMessage):
263
+ return _wrap_in_conversation_event(
264
+ UiPathConversationMessageEvent(
265
+ message_id=message_id,
266
+ tool_call=UiPathConversationToolCallEvent(
267
+ tool_call_id=message.tool_call_id,
268
+ start=UiPathConversationToolCallStartEvent(
269
+ tool_name=message.name or "",
270
+ arguments=UiPathInlineValue(inline=""),
271
+ timestamp=timestamp,
272
+ ),
273
+ end=UiPathConversationToolCallEndEvent(
274
+ timestamp=timestamp,
275
+ result=UiPathInlineValue(inline=message.content),
276
+ ),
277
+ ),
278
+ ),
279
+ exchange_id,
280
+ conversation_id,
281
+ )
282
+
283
+ # --- Fallback ---
284
+ return _wrap_in_conversation_event(
285
+ UiPathConversationMessageEvent(
286
+ message_id=message_id,
287
+ start=UiPathConversationMessageStartEvent(
288
+ role="assistant", timestamp=timestamp
289
+ ),
290
+ content_part=UiPathConversationContentPartEvent(
291
+ content_part_id=f"cp-{message_id}",
292
+ chunk=UiPathConversationContentPartChunkEvent(data=text_content),
293
+ ),
294
+ end=UiPathConversationMessageEndEvent(),
295
+ ),
296
+ exchange_id,
297
+ conversation_id,
298
+ )
@@ -12,6 +12,7 @@ from uipath._cli._runtime._contracts import (
12
12
  from uipath._cli._runtime._hitl import HitlReader
13
13
 
14
14
  from ._context import LangGraphRuntimeContext
15
+ from ._conversation import uipath_to_human_messages
15
16
  from ._exception import LangGraphRuntimeError
16
17
 
17
18
  logger = logging.getLogger(__name__)
@@ -58,6 +59,10 @@ class LangGraphInputProcessor:
58
59
  logger.debug(f"Resumed: {self.context.resume} Input: {self.context.input_json}")
59
60
 
60
61
  if not self.context.resume:
62
+ if self.context.input_message:
63
+ return {
64
+ "messages": uipath_to_human_messages(self.context.input_message)
65
+ }
61
66
  return self.context.input_json
62
67
 
63
68
  if self.context.input_json:
@@ -20,6 +20,7 @@ from ..._utils import _instrument_traceable_attributes
20
20
  from ...tracers import AsyncUiPathTracer
21
21
  from .._utils._graph import LangGraphConfig
22
22
  from ._context import LangGraphRuntimeContext
23
+ from ._conversation import map_message
23
24
  from ._exception import LangGraphRuntimeError
24
25
  from ._input import LangGraphInputProcessor
25
26
  from ._output import LangGraphOutputProcessor
@@ -98,8 +99,27 @@ class LangGraphRuntime(UiPathBaseRuntime):
98
99
  if max_concurrency is not None:
99
100
  graph_config["max_concurrency"] = int(max_concurrency)
100
101
 
102
+ if self.context.chat_handler:
103
+ async for stream_chunk in graph.astream(
104
+ processed_input,
105
+ graph_config,
106
+ stream_mode="messages",
107
+ subgraphs=True,
108
+ ):
109
+ if not isinstance(stream_chunk, tuple) or len(stream_chunk) < 2:
110
+ continue
111
+
112
+ _, (message, _) = stream_chunk
113
+ event = map_message(
114
+ message=message,
115
+ conversation_id=self.context.execution_id,
116
+ exchange_id=self.context.execution_id,
117
+ )
118
+ if event:
119
+ self.context.chat_handler.on_event(event)
120
+
101
121
  # Stream the output at debug time
102
- if self.is_debug_run():
122
+ elif self.is_debug_run():
103
123
  # Get final chunk while streaming
104
124
  final_chunk = None
105
125
  async for stream_chunk in graph.astream(
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: uipath-langchain
3
- Version: 0.0.125
3
+ Version: 0.0.127
4
4
  Summary: UiPath Langchain
5
5
  Project-URL: Homepage, https://uipath.com
6
6
  Project-URL: Repository, https://github.com/UiPath/uipath-langchain-python
@@ -25,7 +25,7 @@ Requires-Dist: openai>=1.65.5
25
25
  Requires-Dist: openinference-instrumentation-langchain>=0.1.50
26
26
  Requires-Dist: pydantic-settings>=2.6.0
27
27
  Requires-Dist: python-dotenv>=1.0.1
28
- Requires-Dist: uipath<2.2.0,>=2.1.33
28
+ Requires-Dist: uipath<2.2.0,>=2.1.41
29
29
  Provides-Extra: langchain
30
30
  Description-Content-Type: text/markdown
31
31
 
@@ -6,10 +6,11 @@ uipath_langchain/_cli/cli_init.py,sha256=xhxJ8tuMSrVUNHvltgyPpOrvgMA-wq9shHeYYwv
6
6
  uipath_langchain/_cli/cli_new.py,sha256=dL8-Rri6u67ZZdbb4nT38A5xD_Q3fVnG0UK9VSeKaqg,2563
7
7
  uipath_langchain/_cli/cli_run.py,sha256=R-cUi3lO3Qd4ysTXD7PW4sa1RsB427v_Y6xUQxWijfQ,3725
8
8
  uipath_langchain/_cli/_runtime/_context.py,sha256=yyzYJDmk2fkH4T5gm4cLGRyXtjLESrpzHBT9euqluTA,817
9
+ uipath_langchain/_cli/_runtime/_conversation.py,sha256=S1KTx_q-La7ikPRT3nBcIp8t-J9CF0QB0DCduQIIB28,11149
9
10
  uipath_langchain/_cli/_runtime/_exception.py,sha256=USKkLYkG-dzjX3fEiMMOHnVUpiXJs_xF0OQXCCOvbYM,546
10
- uipath_langchain/_cli/_runtime/_input.py,sha256=vZ8vfVxvPSaPWmIPghvNx1VRKzbalHsKUMBPiKDvJWM,5492
11
+ uipath_langchain/_cli/_runtime/_input.py,sha256=3IKrZR9xmxYg6l_1TbFpaLOWPDvK8d3nSKIHPGEEGzE,5715
11
12
  uipath_langchain/_cli/_runtime/_output.py,sha256=yJOZPWv2FRUJWv1NRs9JmpB4QMTDXu8jrxoaKrfJvzw,9078
12
- uipath_langchain/_cli/_runtime/_runtime.py,sha256=dRM28l1k3qFf8NLWytRKlOcPwZJ__qUjqmV04wN9JRY,14504
13
+ uipath_langchain/_cli/_runtime/_runtime.py,sha256=9X_8YEny238V1sTb4cjkpd6J69DYQWo6eYVH9kA9gEQ,15383
13
14
  uipath_langchain/_cli/_templates/langgraph.json.template,sha256=eeh391Gta_hoRgaNaZ58nW1LNvCVXA7hlAH6l7Veous,107
14
15
  uipath_langchain/_cli/_templates/main.py.template,sha256=nMJQIYPlRk90iANfNVpkJ2EQX20Dxsyq92-BucEz_UM,1189
15
16
  uipath_langchain/_cli/_utils/_graph.py,sha256=JPShHNl0UQvl4AdjLIqLpNt_JAjpWH9WWF22Gs47Xew,7445
@@ -31,8 +32,8 @@ uipath_langchain/tracers/_instrument_traceable.py,sha256=0e841zVzcPWjOGtmBx0GeHb
31
32
  uipath_langchain/tracers/_utils.py,sha256=JOT1tKMdvqjMDtj2WbmbOWMeMlTXBWavxWpogX7KlRA,1543
32
33
  uipath_langchain/vectorstores/__init__.py,sha256=w8qs1P548ud1aIcVA_QhBgf_jZDrRMK5Lono78yA8cs,114
33
34
  uipath_langchain/vectorstores/context_grounding_vectorstore.py,sha256=TncIXG-YsUlO0R5ZYzWsM-Dj1SVCZbzmo2LraVxXelc,9559
34
- uipath_langchain-0.0.125.dist-info/METADATA,sha256=-qGeRLnvPNVGjyNyQRn5SQ0bgd8Kn07sgfgmVCYfihg,4235
35
- uipath_langchain-0.0.125.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
36
- uipath_langchain-0.0.125.dist-info/entry_points.txt,sha256=FUtzqGOEntlJKMJIXhQUfT7ZTbQmGhke1iCmDWZaQZI,81
37
- uipath_langchain-0.0.125.dist-info/licenses/LICENSE,sha256=JDpt-uotAkHFmxpwxi6gwx6HQ25e-lG4U_Gzcvgp7JY,1063
38
- uipath_langchain-0.0.125.dist-info/RECORD,,
35
+ uipath_langchain-0.0.127.dist-info/METADATA,sha256=-pkEhqb6QF5H9pH-3TVabaJmMZcsWrd-sAH6EMlPhpg,4235
36
+ uipath_langchain-0.0.127.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
37
+ uipath_langchain-0.0.127.dist-info/entry_points.txt,sha256=FUtzqGOEntlJKMJIXhQUfT7ZTbQmGhke1iCmDWZaQZI,81
38
+ uipath_langchain-0.0.127.dist-info/licenses/LICENSE,sha256=JDpt-uotAkHFmxpwxi6gwx6HQ25e-lG4U_Gzcvgp7JY,1063
39
+ uipath_langchain-0.0.127.dist-info/RECORD,,