Skip to content

Commit 97b1789

Browse files
feat: use CreateDeepRagRaw and WaitEphemeralIndexRaw
1 parent 9a58c20 commit 97b1789

File tree

5 files changed

+667
-445
lines changed

5 files changed

+667
-445
lines changed

pyproject.toml

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ requires-python = ">=3.11"
77
dependencies = [
88
"uipath>=2.10.0, <2.11.0",
99
"uipath-core>=0.5.2, <0.6.0",
10-
"uipath-platform>=0.0.8, <0.1.0",
10+
"uipath-platform==0.0.13.dev1014125197",
1111
"uipath-runtime>=0.9.1, <0.10.0",
1212
"langgraph>=1.0.0, <2.0.0",
1313
"langchain-core>=1.2.11, <2.0.0",
@@ -71,6 +71,9 @@ dev = [
7171
"rust-just>=1.39.0",
7272
]
7373

74+
[tool.uv.sources]
75+
uipath-platform = { index = "testpypi" }
76+
7477
[tool.hatch.build.targets.wheel]
7578
packages = ["src/uipath_langchain"]
7679

src/uipath_langchain/agent/tools/context_tool.py

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,12 @@
1313
)
1414
from uipath.eval.mocks import mockable
1515
from uipath.platform import UiPath
16-
from uipath.platform.common import CreateBatchTransform, CreateDeepRag, UiPathConfig
16+
from uipath.platform.common import CreateBatchTransform, CreateDeepRagRaw, UiPathConfig
1717
from uipath.platform.context_grounding import (
1818
BatchTransformOutputColumn,
1919
CitationMode,
2020
DeepRagContent,
21+
DeepRagStatus,
2122
)
2223
from uipath.runtime.errors import UiPathErrorCategory
2324

@@ -200,7 +201,7 @@ async def context_tool_fn(query: Optional[str] = None) -> dict[str, Any]:
200201

201202
@durable_interrupt
202203
async def create_deep_rag():
203-
return CreateDeepRag(
204+
return CreateDeepRagRaw(
204205
name=f"task-{uuid.uuid4()}",
205206
index_name=index_name,
206207
prompt=actual_prompt,
@@ -209,7 +210,16 @@ async def create_deep_rag():
209210
glob_pattern=glob_pattern,
210211
)
211212

212-
return await create_deep_rag()
213+
result = await create_deep_rag()
214+
if result.last_deep_rag_status == DeepRagStatus.FAILED:
215+
return result.failure_reason
216+
217+
if result.content:
218+
content = result.content.model_dump()
219+
content["deepRagId"] = result.id
220+
return content
221+
222+
return {"status": result.last_deep_rag_status, "__internal": "NO_CONTENT"}
213223

214224
return StructuredToolWithOutputType(
215225
name=tool_name,

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

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,12 @@
1212
)
1313
from uipath.eval.mocks import mockable
1414
from uipath.platform import UiPath
15-
from uipath.platform.common import CreateDeepRag, WaitEphemeralIndex
15+
from uipath.platform.common import CreateDeepRagRaw, WaitEphemeralIndexRaw
1616
from uipath.platform.context_grounding import (
1717
CitationMode,
18+
DeepRagStatus,
1819
EphemeralIndexUsage,
20+
IndexStatus,
1921
)
2022
from uipath.platform.context_grounding.context_grounding_index import (
2123
ContextGroundingIndex,
@@ -125,7 +127,7 @@ async def create_ephemeral_index():
125127
)
126128
)
127129
if ephemeral_index.in_progress_ingestion():
128-
return WaitEphemeralIndex(index=ephemeral_index)
130+
return WaitEphemeralIndexRaw(index=ephemeral_index)
129131
return ReadyEphemeralIndex(index=ephemeral_index)
130132

131133
index_result = await create_ephemeral_index()
@@ -134,9 +136,12 @@ async def create_ephemeral_index():
134136
else:
135137
ephemeral_index = index_result
136138

139+
if ephemeral_index.last_ingestion_status == IndexStatus.FAILED:
140+
return ephemeral_index.last_ingestion_failure_reason
141+
137142
@durable_interrupt
138143
async def create_deeprag():
139-
return CreateDeepRag(
144+
return CreateDeepRagRaw(
140145
name=f"task-{uuid.uuid4()}",
141146
index_name=ephemeral_index.name,
142147
index_id=ephemeral_index.id,
@@ -147,7 +152,15 @@ async def create_deeprag():
147152

148153
result = await create_deeprag()
149154

150-
return result
155+
if result.last_deep_rag_status == DeepRagStatus.FAILED:
156+
return result.failure_reason
157+
158+
if result.content:
159+
content = result.content.model_dump()
160+
content["deepRagId"] = result.id
161+
return content
162+
163+
return {"status": result.last_deep_rag_status, "__internal": "NO_CONTENT"}
151164

152165
return await invoke_deeprag(**kwargs)
153166

tests/agent/tools/internal_tools/test_deeprag_tool.py

Lines changed: 188 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@
1616
DeepRagFileExtension,
1717
DeepRagFileExtensionSetting,
1818
)
19+
from uipath.platform.context_grounding import DeepRagResponse, DeepRagStatus, IndexStatus
20+
from uipath.platform.context_grounding.context_grounding import DeepRagContent
1921
from uipath.platform.context_grounding.context_grounding_index import (
2022
ContextGroundingIndex,
2123
)
@@ -152,8 +154,16 @@ async def test_create_deeprag_tool_static_query_index_ready(
152154

153155
# Index is ready → ReadyEphemeralIndex skips interrupt() (no scratchpad in tests).
154156
# Only create_deeprag calls interrupt().
157+
deeprag_id = str(uuid.uuid4())
155158
mock_interrupt.side_effect = [
156-
{"text": "Deep RAG analysis result"},
159+
DeepRagResponse(
160+
id=deeprag_id,
161+
name="test-deeprag",
162+
created_date="2024-01-01",
163+
last_deep_rag_status=DeepRagStatus.SUCCESSFUL,
164+
content=DeepRagContent(text="Deep RAG analysis result", citations=[]),
165+
failure_reason=None,
166+
),
157167
]
158168

159169
mock_wrapper = Mock()
@@ -175,7 +185,7 @@ async def test_create_deeprag_tool_static_query_index_ready(
175185
result = await tool.coroutine(attachment=mock_attachment)
176186

177187
# Verify result
178-
assert result == {"text": "Deep RAG analysis result"}
188+
assert result == {"text": "Deep RAG analysis result", "citations": [], "deepRagId": deeprag_id}
179189

180190
# Verify ephemeral index was created
181191
mock_uipath.context_grounding.create_ephemeral_index_async.assert_called_once()
@@ -228,9 +238,17 @@ async def test_create_deeprag_tool_static_query_wait_for_ingestion(
228238
)
229239

230240
# First interrupt returns completed index, second returns DeepRAG result
241+
deeprag_id = str(uuid.uuid4())
231242
mock_interrupt.side_effect = [
232243
mock_index_complete,
233-
{"text": "Deep RAG analysis after waiting"},
244+
DeepRagResponse(
245+
id=deeprag_id,
246+
name="test-deeprag",
247+
created_date="2024-01-01",
248+
last_deep_rag_status=DeepRagStatus.SUCCESSFUL,
249+
content=DeepRagContent(text="Deep RAG analysis after waiting", citations=[]),
250+
failure_reason=None,
251+
),
234252
]
235253

236254
mock_wrapper = Mock()
@@ -248,7 +266,7 @@ async def test_create_deeprag_tool_static_query_wait_for_ingestion(
248266
result = await tool.coroutine(attachment=mock_attachment)
249267

250268
# Verify result
251-
assert result == {"text": "Deep RAG analysis after waiting"}
269+
assert result == {"text": "Deep RAG analysis after waiting", "citations": [], "deepRagId": deeprag_id}
252270

253271
# Verify interrupt was called twice (WaitEphemeralIndex + CreateDeepRag)
254272
assert mock_interrupt.call_count == 2
@@ -286,8 +304,16 @@ async def test_create_deeprag_tool_dynamic_query(
286304
)
287305

288306
# Index is ready → ReadyEphemeralIndex skips interrupt(). Only create_deeprag fires.
307+
deeprag_id = str(uuid.uuid4())
289308
mock_interrupt.side_effect = [
290-
{"content": "Dynamic query result"},
309+
DeepRagResponse(
310+
id=deeprag_id,
311+
name="test-deeprag",
312+
created_date="2024-01-01",
313+
last_deep_rag_status=DeepRagStatus.SUCCESSFUL,
314+
content=DeepRagContent(text="Dynamic query result", citations=[]),
315+
failure_reason=None,
316+
),
291317
]
292318

293319
mock_wrapper = Mock()
@@ -307,7 +333,7 @@ async def test_create_deeprag_tool_dynamic_query(
307333
)
308334

309335
# Verify result
310-
assert result == {"content": "Dynamic query result"}
336+
assert result == {"text": "Dynamic query result", "citations": [], "deepRagId": deeprag_id}
311337

312338
@patch(
313339
"uipath_langchain.agent.wrappers.job_attachment_wrapper.get_job_attachment_wrapper"
@@ -345,6 +371,162 @@ async def test_create_deeprag_tool_missing_query_dynamic(
345371
with pytest.raises(ValueError, match="Query is required for DeepRAG tool"):
346372
await tool.coroutine(attachment=mock_attachment)
347373

374+
@patch(
375+
"uipath_langchain.agent.wrappers.job_attachment_wrapper.get_job_attachment_wrapper"
376+
)
377+
@patch("uipath_langchain.agent.tools.internal_tools.deeprag_tool.UiPath")
378+
@patch("uipath_langchain.agent.tools.durable_interrupt.decorator.interrupt")
379+
@patch(
380+
"uipath_langchain.agent.tools.internal_tools.deeprag_tool.mockable",
381+
lambda **kwargs: lambda f: f,
382+
)
383+
async def test_invoke_returns_error_message_on_failed_deeprag(
384+
self,
385+
mock_interrupt,
386+
mock_uipath_class,
387+
mock_get_wrapper,
388+
resource_config_static,
389+
mock_llm,
390+
):
391+
"""Test that tool returns failure_reason string when DeepRAG processing fails."""
392+
mock_uipath = AsyncMock()
393+
mock_uipath_class.return_value = mock_uipath
394+
395+
mock_index = ContextGroundingIndex(
396+
id=str(uuid.uuid4()),
397+
name="ephemeral-index-123",
398+
last_ingestion_status=IndexStatus.SUCCESSFUL,
399+
)
400+
mock_uipath.context_grounding.create_ephemeral_index_async = AsyncMock(
401+
return_value=mock_index
402+
)
403+
404+
failed_deep_rag = DeepRagResponse(
405+
id=str(uuid.uuid4()),
406+
name="test-deeprag",
407+
created_date="2024-01-01",
408+
last_deep_rag_status=DeepRagStatus.FAILED,
409+
content=None,
410+
failure_reason="DeepRAG processing failed due to an internal error",
411+
)
412+
413+
# Index is ready (no interrupt for index), DeepRAG fails
414+
mock_interrupt.side_effect = [failed_deep_rag]
415+
416+
mock_wrapper = Mock()
417+
mock_get_wrapper.return_value = mock_wrapper
418+
419+
tool = create_deeprag_tool(resource_config_static, mock_llm)
420+
421+
mock_attachment = MockAttachment(
422+
ID=str(uuid.uuid4()), FullName="test.pdf", MimeType="application/pdf"
423+
)
424+
425+
assert tool.coroutine is not None
426+
result = await tool.coroutine(attachment=mock_attachment)
427+
428+
assert result == "DeepRAG processing failed due to an internal error"
429+
assert mock_interrupt.call_count == 1
430+
431+
@patch(
432+
"uipath_langchain.agent.wrappers.job_attachment_wrapper.get_job_attachment_wrapper"
433+
)
434+
@patch("uipath_langchain.agent.tools.internal_tools.deeprag_tool.UiPath")
435+
@patch(
436+
"uipath_langchain.agent.tools.internal_tools.deeprag_tool.mockable",
437+
lambda **kwargs: lambda f: f,
438+
)
439+
async def test_invoke_returns_error_message_on_failed_ephemeral_index(
440+
self,
441+
mock_uipath_class,
442+
mock_get_wrapper,
443+
resource_config_static,
444+
mock_llm,
445+
):
446+
"""Test that tool returns failure reason when ephemeral index fails immediately."""
447+
mock_uipath = AsyncMock()
448+
mock_uipath_class.return_value = mock_uipath
449+
450+
mock_index = ContextGroundingIndex(
451+
id=str(uuid.uuid4()),
452+
name="ephemeral-index-123",
453+
last_ingestion_status=IndexStatus.FAILED,
454+
last_ingestion_failure_reason="Ingestion failed due to unsupported file format",
455+
)
456+
mock_uipath.context_grounding.create_ephemeral_index_async = AsyncMock(
457+
return_value=mock_index
458+
)
459+
460+
mock_wrapper = Mock()
461+
mock_get_wrapper.return_value = mock_wrapper
462+
463+
tool = create_deeprag_tool(resource_config_static, mock_llm)
464+
465+
mock_attachment = MockAttachment(
466+
ID=str(uuid.uuid4()), FullName="test.pdf", MimeType="application/pdf"
467+
)
468+
469+
assert tool.coroutine is not None
470+
result = await tool.coroutine(attachment=mock_attachment)
471+
472+
assert result == "Ingestion failed due to unsupported file format"
473+
474+
@patch(
475+
"uipath_langchain.agent.wrappers.job_attachment_wrapper.get_job_attachment_wrapper"
476+
)
477+
@patch("uipath_langchain.agent.tools.internal_tools.deeprag_tool.UiPath")
478+
@patch("uipath_langchain.agent.tools.durable_interrupt.decorator.interrupt")
479+
@patch(
480+
"uipath_langchain.agent.tools.internal_tools.deeprag_tool.mockable",
481+
lambda **kwargs: lambda f: f,
482+
)
483+
async def test_invoke_returns_error_message_on_failed_ephemeral_index_after_wait(
484+
self,
485+
mock_interrupt,
486+
mock_uipath_class,
487+
mock_get_wrapper,
488+
resource_config_static,
489+
mock_llm,
490+
):
491+
"""Test that tool returns failure reason when ephemeral index fails after waiting."""
492+
mock_uipath = AsyncMock()
493+
mock_uipath_class.return_value = mock_uipath
494+
495+
pending_id = str(uuid.uuid4())
496+
mock_index_pending = ContextGroundingIndex(
497+
id=pending_id,
498+
name="ephemeral-index-456",
499+
last_ingestion_status=IndexStatus.IN_PROGRESS,
500+
)
501+
mock_uipath.context_grounding.create_ephemeral_index_async = AsyncMock(
502+
return_value=mock_index_pending
503+
)
504+
505+
mock_index_failed = {
506+
"id": pending_id,
507+
"name": mock_index_pending.name,
508+
"last_ingestion_status": "Failed",
509+
"last_ingestion_failure_reason": "Ingestion failed during processing",
510+
}
511+
512+
# First (and only) interrupt returns the failed index; DeepRAG is never reached
513+
mock_interrupt.side_effect = [mock_index_failed]
514+
515+
mock_wrapper = Mock()
516+
mock_get_wrapper.return_value = mock_wrapper
517+
518+
tool = create_deeprag_tool(resource_config_static, mock_llm)
519+
520+
mock_attachment = MockAttachment(
521+
ID=str(uuid.uuid4()), FullName="test.pdf", MimeType="application/pdf"
522+
)
523+
524+
assert tool.coroutine is not None
525+
result = await tool.coroutine(attachment=mock_attachment)
526+
527+
assert result == "Ingestion failed during processing"
528+
assert mock_interrupt.call_count == 1
529+
348530
@patch(
349531
"uipath_langchain.agent.wrappers.job_attachment_wrapper.get_job_attachment_wrapper"
350532
)

0 commit comments

Comments
 (0)