Skip to content

Commit 1e59f96

Browse files
feat: add citation and reasoning mappings
1 parent 9e94bda commit 1e59f96

File tree

4 files changed

+67
-24
lines changed

4 files changed

+67
-24
lines changed

pyproject.toml

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[project]
22
name = "uipath-langchain"
3-
version = "0.1.18"
3+
version = "0.1.19"
44
description = "UiPath Langchain"
55
readme = { file = "README.md", content-type = "text/markdown" }
66
requires-python = ">=3.11"
@@ -113,4 +113,3 @@ name = "testpypi"
113113
url = "https://test.pypi.org/simple/"
114114
publish-url = "https://test.pypi.org/legacy/"
115115
explicit = true
116-

src/uipath_langchain/chat/mapper.py

Lines changed: 53 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,17 @@
77
from langchain_core.messages import (
88
AIMessageChunk,
99
BaseMessage,
10+
Citation,
1011
HumanMessage,
1112
TextContentBlock,
1213
ToolCallChunk,
1314
ToolMessage,
1415
)
1516
from uipath.core.chat import (
17+
UiPathConversationCitationEndEvent,
18+
UiPathConversationCitationEvent,
19+
UiPathConversationCitationSource,
20+
UiPathConversationCitationStartEvent,
1621
UiPathConversationContentPartChunkEvent,
1722
UiPathConversationContentPartEndEvent,
1823
UiPathConversationContentPartEvent,
@@ -184,13 +189,50 @@ def map_event(
184189
text_block = cast(TextContentBlock, block)
185190
text = text_block["text"]
186191

187-
msg_event.content_part = UiPathConversationContentPartEvent(
188-
content_part_id=f"chunk-{message.id}-0",
189-
chunk=UiPathConversationContentPartChunkEvent(
190-
data=text,
191-
content_part_sequence=0,
192-
),
193-
)
192+
# Map citations if present
193+
annotations = text_block.get("annotations", [])
194+
citation_annotations = [
195+
cast(Citation, annotation)
196+
for annotation in annotations
197+
if annotation.get("type") == "citation"
198+
]
199+
200+
if citation_annotations:
201+
for citation_annotation in citation_annotations:
202+
# Build citation source, only include url if present
203+
block_index = text_block.get("index", 0)
204+
block_index = block_index // 2 + 1
205+
source_args = {
206+
"title": citation_annotation.get("title"),
207+
"number": block_index,
208+
}
209+
if citation_annotation.get("url") is not None:
210+
source_args["url"] = citation_annotation.get("url")
211+
212+
citation_source = UiPathConversationCitationSource(
213+
**source_args
214+
)
215+
216+
msg_event.content_part = UiPathConversationContentPartEvent(
217+
content_part_id=f"chunk-{message.id}-0",
218+
chunk=UiPathConversationContentPartChunkEvent(
219+
data=citation_annotation["cited_text"],
220+
citation=UiPathConversationCitationEvent(
221+
citation_id=str(uuid4()),
222+
start=UiPathConversationCitationStartEvent(),
223+
end=UiPathConversationCitationEndEvent(
224+
sources=[citation_source]
225+
),
226+
),
227+
),
228+
)
229+
else:
230+
msg_event.content_part = UiPathConversationContentPartEvent(
231+
content_part_id=f"chunk-{message.id}-0",
232+
chunk=UiPathConversationContentPartChunkEvent(
233+
data=text
234+
),
235+
)
194236

195237
elif block_type == "tool_call_chunk":
196238
tool_chunk_block = cast(ToolCallChunk, block)
@@ -204,10 +246,7 @@ def map_event(
204246

205247
msg_event.content_part = UiPathConversationContentPartEvent(
206248
content_part_id=f"chunk-{message.id}-0",
207-
chunk=UiPathConversationContentPartChunkEvent(
208-
data=args,
209-
content_part_sequence=0,
210-
),
249+
chunk=UiPathConversationContentPartChunkEvent(data=args),
211250
)
212251
# Continue so that multiple tool_call_chunks in the same block list
213252
# are handled correctly
@@ -217,10 +256,7 @@ def map_event(
217256
elif isinstance(message.content, str) and message.content:
218257
msg_event.content_part = UiPathConversationContentPartEvent(
219258
content_part_id=f"content-{message.id}",
220-
chunk=UiPathConversationContentPartChunkEvent(
221-
data=message.content,
222-
content_part_sequence=0,
223-
),
259+
chunk=UiPathConversationContentPartChunkEvent(data=message.content),
224260
)
225261

226262
if (
@@ -269,12 +305,12 @@ def map_event(
269305
tool_call_id=message.tool_call_id,
270306
start=UiPathConversationToolCallStartEvent(
271307
tool_name=message.name,
272-
arguments=None,
308+
input=None,
273309
timestamp=timestamp,
274310
),
275311
end=UiPathConversationToolCallEndEvent(
276312
timestamp=timestamp,
277-
result=UiPathInlineValue(inline=content_value),
313+
output=UiPathInlineValue(inline=content_value),
278314
),
279315
),
280316
)

src/uipath_langchain/runtime/runtime.py

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
from langgraph.errors import EmptyInputError, GraphRecursionError, InvalidUpdateError
88
from langgraph.graph.state import CompiledStateGraph
99
from langgraph.types import Command, Interrupt, StateSnapshot
10+
from pydantic import ValidationError
1011
from uipath.runtime import (
1112
UiPathBreakpointResult,
1213
UiPathExecuteOptions,
@@ -135,10 +136,17 @@ async def stream(
135136
if chunk_type == "messages":
136137
if isinstance(data, tuple):
137138
message, _ = data
138-
event = UiPathRuntimeMessageEvent(
139-
payload=self.chat.map_event(message),
140-
)
141-
yield event
139+
140+
try:
141+
mapped_event = self.chat.map_event(message)
142+
event = UiPathRuntimeMessageEvent(
143+
payload=mapped_event,
144+
)
145+
yield event
146+
except ValidationError as e:
147+
logger.warning(
148+
f"Failed to map event due to validation error, skipping: {e}"
149+
)
142150

143151
# Emit UiPathRuntimeStateEvent for state updates
144152
elif chunk_type == "updates":

uv.lock

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)